tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

app.js (49371B)


      1 import { exampleGroups, exampleGroup, selectorLibraries, libraryLabel } from './program-catalogue.js';
      2 import { importPicker } from './import-picker.js';
      3 import { localModulesUI } from './local-modules-ui.js';
      4 import { outputReveal } from './output-reveal.js';
      5 import { syntaxEditor, tokenize, paintSyntax } from './syntax.js';
      6 import { printLiteral } from './literal-input.js';
      7 import { backend, backends, runtimeStats } from './runtime-backends.js';
      8 import { demoInputLabels } from './demo-inputs.js';
      9 import { argumentEditor } from './arguments-ui.js';
     10 import { createOutput } from './output-app.js';
     11 import { setupInspector } from './inspector.js';
     12 import { isTreeResult } from './tree-result.js';
     13 import { treeFormat } from './tree-export.js';
     14 import { ActiveClock } from './clock.js';
     15 import { PACKAGE_LOAD_MS, EXPORT_ACTIVE_MS, operationFailure, operationTimeout } from './execution.js';
     16 import { sourceText } from './compiler.js';
     17 import { packageIndex, packageReleases } from './packages.js';
     18 import { bundleHash, fetchBytes } from './bundle.js';
     19 import { Graph } from './graph.js';
     20 import { line, literals, outputType } from './codecs.js';
     21 function el(id) {
     22     const element = document.getElementById(id);
     23     if (!element)
     24         throw new Error(`Missing element ${id}`);
     25     return element;
     26 }
     27 const sourceEditor = el('source-editor'), compileButton = el('compile');
     28 const restoreButton = el('restore-source'), downloadButton = el('download-bundle');
     29 let sourceAvailable = false;
     30 const expressionEditor = el('expression-editor');
     31 const refreshSourceSyntax = syntaxEditor(sourceEditor), refreshExpressionSyntax = syntaxEditor(expressionEditor);
     32 const revealOutput = outputReveal(document.querySelector('.editor-toolbar'));
     33 const guidedInputs = el('use-guided-inputs');
     34 let editRevision = 0, composing = false, idleDue = 0;
     35 let idleTimer;
     36 function cancelIdleCompile() { clearTimeout(idleTimer); idleTimer = undefined; }
     37 function queueCompile(delay = 650) {
     38     cancelIdleCompile();
     39     if (composing)
     40         return;
     41     idleDue = performance.now() + delay;
     42     idleTimer = setTimeout(() => {
     43         idleTimer = undefined;
     44         if (!busy && draft)
     45             compileDraft();
     46     }, delay);
     47 }
     48 function markPreviousResult() {
     49     revealOutput.cancel();
     50     if (el('result').textContent || el('terminal').textContent)
     51         el('result-stale').hidden = false;
     52 }
     53 function presetExpression(name = exportsInput.value || preferredName) {
     54     expressionEditor.value = [name, ...argumentsUI.read().map(printLiteral)].join(' ');
     55     refreshExpressionSyntax();
     56 }
     57 function expressionInput() {
     58     return guidedInputs.checked ? {} : { expression: sourceText(expressionEditor.value) };
     59 }
     60 const backendPicker = el('runtime-backend');
     61 let selectedBackend = backend(backendPicker.value);
     62 const workerURL = () => new URL(backends[selectedBackend].worker, import.meta.url);
     63 let draft = false, scratch = false, compiled = false, busy = false, originalSource;
     64 let compiledSource, compiledRevision;
     65 let compilationStats = '', preferredName = 'id';
     66 let compilerTimer;
     67 function stopCompilerActivity() { clearInterval(compilerTimer); compilerTimer = undefined; el('compile-activity').hidden = true; }
     68 let releases = [];
     69 const programPicker = el('program-picker');
     70 const versionPicker = el('package-version');
     71 let versionPackage;
     72 const hashInput = el('bundle-hash'), exportsInput = el('export');
     73 const argsInput = el('arguments'), resultCodec = el('output-type');
     74 const argumentsUI = argumentEditor(argsInput);
     75 const mode = el('mode'), steps = el('step-mode');
     76 const runButton = el('run'), stopButton = el('stop'), nextButton = el('next');
     77 let operation;
     78 const deadline = new ActiveClock(() => finish(operationTimeout(operation ?? 'loading'), true, 'timeout'));
     79 const shareButton = el('share');
     80 const outputUI = createOutput();
     81 const outputTarget = el('output-target');
     82 const storedOutput = () => outputTarget.value === 'definition';
     83 const visibleCodec = (value) => value === 'tree' || value === 'list' ? 'tricu' : value;
     84 let resultInterpretation = 'tricu';
     85 function clearTreeExport() { if (storedOutput())
     86     outputUI.clear(); }
     87 function syncOutputTarget() {
     88     for (const option of resultCodec.options)
     89         option.disabled = storedOutput() && !isTreeResult(option.value);
     90     if (storedOutput() && !isTreeResult(resultCodec.value))
     91         resultCodec.value = 'tricu';
     92     controls(busy);
     93 }
     94 outputTarget.onchange = () => {
     95     if (busy)
     96         return;
     97     revealOutput.cancel();
     98     if (storedOutput())
     99         resultInterpretation = resultCodec.value;
    100     else
    101         resultCodec.value = resultInterpretation;
    102     clearOutput();
    103     syncOutputTarget();
    104 };
    105 const inputForm = el('input-form'), lineInput = el('line-input');
    106 let entries = [], selected;
    107 let selection;
    108 let loaded = false, worker, pending;
    109 let automaticRun = false;
    110 let savedRun;
    111 let preferredExport;
    112 const traceRows = new Map();
    113 const localModules = localModulesUI(() => {
    114     if (!draft)
    115         return;
    116     if (busy && operation !== 'compiling')
    117         return;
    118     editRevision++;
    119     markPreviousResult();
    120     if (!busy) {
    121         dispose();
    122         el('status').textContent = 'Changes pending';
    123     }
    124     queueCompile();
    125 });
    126 const imports = importPicker({ source: sourceEditor, backend: () => backendPicker.value,
    127     available: () => !busy && !sourceEditor.hidden && !sourceEditor.readOnly,
    128     local: () => localModules.entries(), inputs: () => localModules.inputs() });
    129 const exportModuleButton = el('export-compiled-module');
    130 exportModuleButton.onclick = () => {
    131     if (busy || !compiled || !draft || sourceEditor.value !== compiledSource || editRevision !== compiledRevision || !worker)
    132         return;
    133     cancelIdleCompile();
    134     operation = 'exporting';
    135     controls(true);
    136     deadline.reset(Infinity);
    137     clock();
    138     el('status').textContent = 'Exporting module';
    139     el('compiled-module-status').textContent = 'Observing public trees; Stop is available…';
    140     worker.postMessage({ type: 'export-module' });
    141 };
    142 function pauseClock() { deadline.pause(); }
    143 function clock() { deadline.start(); }
    144 function runClock() { operation = 'running'; deadline.reset(Infinity); clock(); }
    145 function controls(working) {
    146     busy = working;
    147     localModules.disable(busy);
    148     exportModuleButton.disabled = busy || !draft || !compiled || sourceEditor.value !== compiledSource || editRevision !== compiledRevision;
    149     backendPicker.disabled = busy;
    150     programPicker.disabled = busy || entries.length === 0;
    151     versionPicker.disabled = busy;
    152     hashInput.disabled = busy;
    153     el('load-release').disabled = busy || !programPackage(programPicker.value);
    154     el('load').disabled = busy;
    155     runButton.disabled = busy || (storedOutput() ? draft || !loaded : draft ? !compiled || sourceEditor.value !== compiledSource || editRevision !== compiledRevision : !loaded);
    156     runButton.textContent = storedOutput() ? 'Show definition' : 'Evaluate';
    157     el('draft-run-note').hidden = !draft || (compiled && sourceEditor.value === compiledSource && editRevision === compiledRevision);
    158     el('draft-run-note').textContent = compiled
    159         ? 'Source changed. Evaluation waits for the current revision to compile.'
    160         : 'Waiting for compilation. Published programs are never used as a fallback.';
    161     argumentsUI.labels(!draft && !scratch && selected ? demoInputLabels(selected.id, exportsInput.value || preferredName) : []);
    162     el('io-console').hidden = storedOutput() || mode.value !== 'io' && !el('terminal').textContent;
    163     nextButton.hidden = storedOutput() || !steps.checked;
    164     runButton.classList.toggle('draft', draft);
    165     runButton.classList.toggle('primary', !draft || compiled);
    166     compileButton.classList.remove('primary');
    167     compileButton.disabled = busy || !draft || (compiled && sourceEditor.value === compiledSource && editRevision === compiledRevision);
    168     compileButton.hidden = !draft;
    169     restoreButton.hidden = !draft;
    170     sourceEditor.disabled = busy && operation !== 'compiling';
    171     sourceEditor.readOnly = originalSource === undefined && !draft;
    172     const editableSource = !sourceEditor.hidden && (draft || originalSource !== undefined);
    173     el('add-import').hidden = !editableSource;
    174     el('add-import').disabled = busy;
    175     el('source-title').textContent = editableSource ? 'Source' : sourceAvailable ? 'Read-only source' : loaded ? 'Compiled package (no editable source)' : 'Package information';
    176     el('source-availability').hidden = editableSource || !loaded;
    177     el('source-availability').textContent = sourceAvailable
    178         ? 'Source is shown for reference only; the published implementation cannot be edited here. Evaluate expressions against its exports below.'
    179         : 'This is a compiled package. Editable source is not included; you can evaluate expressions against its exports or export its stored definitions.';
    180     restoreButton.disabled = busy || originalSource === undefined || !draft;
    181     downloadButton.disabled = busy || draft || downloadHash() === undefined;
    182     el('authorship').textContent = draft ? 'Local draft · your authored code (not published)' : loaded ? 'Published package · verified executable' : 'Published selection · not yet verified';
    183     const sourceModified = draft && (!compiled || sourceEditor.value !== compiledSource || editRevision !== compiledRevision);
    184     el('authorship').classList.toggle('modified', sourceModified);
    185     sourceEditor.classList.toggle('modified', sourceModified);
    186     if (draft && storedOutput()) {
    187         el('draft-run-note').hidden = false;
    188         el('draft-run-note').textContent = 'Stored definitions require a published package. Choose Evaluated result or restore the published source.';
    189     }
    190     for (const button of document.querySelectorAll('[data-compiler-example]'))
    191         button.disabled = busy;
    192     stopButton.disabled = !busy && !draft;
    193     stopButton.hidden = !busy;
    194     el('stop-run').disabled = stopButton.disabled;
    195     exportsInput.disabled = busy || (draft ? !compiled : !loaded);
    196     argsInput.disabled = busy;
    197     argumentsUI.disable(busy);
    198     expressionEditor.disabled = busy && operation !== 'compiling';
    199     guidedInputs.disabled = busy;
    200     el('expression-inputs').hidden = storedOutput() || guidedInputs.checked;
    201     el('guided-inputs').hidden = storedOutput() || !guidedInputs.checked;
    202     mode.disabled = busy || storedOutput();
    203     outputTarget.disabled = busy;
    204     resultCodec.disabled = busy;
    205     steps.disabled = busy;
    206     shareButton.disabled = busy || draft || !loaded;
    207     el('reset').disabled = busy;
    208     outputUI.controls(busy);
    209     refreshSourceSyntax();
    210     refreshExpressionSyntax();
    211 }
    212 function dispose() {
    213     cancelIdleCompile();
    214     stopCompilerActivity();
    215     pauseClock();
    216     operation = undefined;
    217     worker?.terminate();
    218     worker = undefined;
    219     compiledSource = undefined;
    220     compiledRevision = undefined;
    221     compiled = false;
    222     pending = undefined;
    223     inputForm.hidden = true;
    224     nextButton.disabled = true;
    225     controls(false);
    226 }
    227 function clearOutput() {
    228     el('result-stale').hidden = true;
    229     el('compile-diagnostic').hidden = true;
    230     el('terminal').textContent = '';
    231     el('result').textContent = '';
    232     el('result').className = '';
    233     outputUI.clear();
    234     el('trace').replaceChildren();
    235     traceRows.clear();
    236     el('trace-empty').hidden = false;
    237     lineInput.value = '';
    238 }
    239 function finish(message, error = false, reason = 'failure') {
    240     if (error && reason === 'failure')
    241         message = operationFailure(operation, message);
    242     if (operation === 'exporting' && draft)
    243         el('compiled-module-status').textContent = message;
    244     if (operation === 'compiling' && reason === 'failure') {
    245         dispose();
    246         el('status').textContent = 'Compilation failed';
    247         el('compiler-stats').textContent = 'Compilation failed; runtime discarded.';
    248         el('compile-diagnostic').textContent = message;
    249         el('compile-diagnostic').hidden = false;
    250         return;
    251     }
    252     if (!draft && !loaded)
    253         el('package-info').textContent = `Package not loaded. ${message}`;
    254     if (error && operation !== 'downloading')
    255         outputUI.clear();
    256     if (operation === 'downloading') {
    257         el('bundle-download-status').textContent = message;
    258         el('bundle-download-status').classList.toggle('error', error);
    259     }
    260     const downloadOnly = operation === 'downloading';
    261     dispose();
    262     el('status').textContent = error ? 'Stopped' : 'Finished';
    263     if (!downloadOnly) {
    264         el('result').textContent = message;
    265         el('result').className = error ? 'error' : '';
    266         revealOutput.reveal(el('result'));
    267     }
    268     for (const row of traceRows.values())
    269         row.classList.remove('pending');
    270 }
    271 function setPreset(entry, name = entry.export) {
    272     const preset = entry.presets?.[name] ?? entry;
    273     argumentsUI.labels(demoInputLabels(entry.id, name));
    274     argsInput.value = JSON.stringify(preset.arguments, null, 2);
    275     argumentsUI.refresh(true);
    276     resultCodec.value = visibleCodec(preset.output);
    277     mode.value = preset.mode;
    278     presetExpression(name);
    279 }
    280 function startExport(message, send) {
    281     operation = 'exporting';
    282     clearOutput();
    283     controls(true);
    284     deadline.reset(EXPORT_ACTIVE_MS);
    285     clock();
    286     el('status').textContent = 'Exporting definition';
    287     send(message);
    288 }
    289 function newWorker(runAfterLoad, preserveInputs = false, exportAfterLoad) {
    290     if (!selection)
    291         return;
    292     dispose();
    293     operation = 'loading';
    294     deadline.reset(PACKAGE_LOAD_MS);
    295     automaticRun = runAfterLoad;
    296     const current = new Worker(workerURL(), { type: 'module' });
    297     worker = current;
    298     let currentVerified = false;
    299     controls(true);
    300     el('status').textContent = 'Loading package';
    301     el('package-info').textContent = 'Verifying package…';
    302     clock();
    303     const send = (message) => current.postMessage(message);
    304     current.onmessage = ({ data }) => {
    305         if (worker !== current)
    306             return;
    307         switch (data.type) {
    308             case 'state':
    309                 if (data.state === 'running') {
    310                     pending = undefined;
    311                     clock();
    312                     el('status').textContent = operation === 'loading' ? 'Loading package' : 'Running';
    313                 }
    314                 else {
    315                     pauseClock();
    316                     pending = { kind: data.state, id: data.id };
    317                     el('status').textContent = data.state === 'input' ? 'Waiting for input' : 'Paused before action';
    318                     inputForm.hidden = data.state !== 'input';
    319                     nextButton.disabled = data.state !== 'step';
    320                     revealOutput.reveal(data.state === 'input' ? inputForm : nextButton);
    321                 }
    322                 break;
    323             case 'loaded': {
    324                 currentVerified = true;
    325                 loaded = true;
    326                 el('compiler-stats').textContent = 'Published package · no local compilation.';
    327                 const oldExport = exportAfterLoad?.export ?? (automaticRun ? savedRun?.export : preferredExport ?? selected?.export);
    328                 exportsInput.replaceChildren();
    329                 for (const item of data.exports) {
    330                     const option = document.createElement('option');
    331                     option.value = item.name;
    332                     option.textContent = item.name;
    333                     exportsInput.append(option);
    334                 }
    335                 if (oldExport && data.exports.some(e => e.name === oldExport))
    336                     exportsInput.value = oldExport;
    337                 preferredExport = undefined;
    338                 el('package-info').textContent = `SHA-256 verified · ${data.nodes.toLocaleString()} stored DAG nodes · ${data.exports.length} export(s) · ${data.hash}`;
    339                 if (!preserveInputs && !automaticRun && !exportAfterLoad) {
    340                     originalSource = selected?.environment !== undefined && data.source !== null ? data.source : undefined;
    341                     sourceEditor.value = originalSource ?? '';
    342                     sourceEditor.hidden = originalSource === undefined;
    343                     el('source-notes').hidden = originalSource !== undefined;
    344                 }
    345                 if (!preserveInputs && !automaticRun && !exportAfterLoad)
    346                     presetExpression();
    347                 if (storedOutput() && !isTreeResult(resultCodec.value))
    348                     resultCodec.value = 'tricu';
    349                 sourceAvailable = data.source !== null;
    350                 el('source-label').textContent = data.source === null ? 'Package metadata' : 'Curated tricu / stable';
    351                 el('source').textContent = data.source ?? `Verified package metadata\n${JSON.stringify(data.metadata, null, 2)}\n\nChoose an export, its arguments and a result interpretation to apply this tree.`;
    352                 if (data.source !== null)
    353                     paintSyntax(el('source'), tokenize(data.source));
    354                 if (exportAfterLoad) {
    355                     pauseClock();
    356                     startExport(exportAfterLoad, send);
    357                 }
    358                 else if (automaticRun && savedRun) {
    359                     pauseClock();
    360                     runClock();
    361                     automaticRun = false;
    362                     send(savedRun);
    363                 }
    364                 else {
    365                     if (!preserveInputs && selected?.presets?.[exportsInput.value])
    366                         setPreset(selected, exportsInput.value);
    367                     pauseClock();
    368                     operation = undefined;
    369                     controls(false);
    370                     el('status').textContent = 'Ready';
    371                 }
    372                 break;
    373             }
    374             case 'exported': {
    375                 pauseClock();
    376                 if (data.hash !== selection?.hash || data.export !== exportsInput.value || data.format !== resultCodec.value || !storedOutput())
    377                     break;
    378                 outputUI.file(data.file, data.export, `Stored definition ${data.export} · SHA-256 ${data.hash} · ${data.format}`);
    379                 revealOutput.reveal(el('result'));
    380                 operation = undefined;
    381                 controls(false);
    382                 el('status').textContent = 'Ready';
    383                 break;
    384             }
    385             case 'export-error':
    386                 finish(data.message, true);
    387                 break;
    388             case 'trace': {
    389                 el('trace-empty').hidden = true;
    390                 const event = data.event;
    391                 if (data.phase === 'before') {
    392                     const row = document.createElement('li');
    393                     row.className = 'pending';
    394                     row.textContent = `${String(event.step).padStart(2, '0')}  ${event.name} ${JSON.stringify(data.event.detail)}  [stack ${data.event.depth}]`;
    395                     traceRows.set(event.step, row);
    396                     el('trace').append(row);
    397                 }
    398                 else {
    399                     const row = traceRows.get(event.step);
    400                     if (row) {
    401                         row.textContent += `\n    → ${JSON.stringify(data.event.value)}`;
    402                         row.classList.remove('pending');
    403                     }
    404                 }
    405                 el('trace').scrollTop = el('trace').scrollHeight;
    406                 break;
    407             }
    408             case 'output':
    409                 el('terminal').textContent += data.text;
    410                 revealOutput.reveal(el('io-console'));
    411                 el('terminal').scrollTop = el('terminal').scrollHeight;
    412                 break;
    413             case 'done': {
    414                 idleDraft();
    415                 el('status').textContent = 'Finished';
    416                 outputUI.result(data.result, `Evaluated result · ${backends[selectedBackend].label}`);
    417                 revealOutput.reveal(el('result'));
    418                 el('compiler-stats').textContent = `Evaluation · ${runtimeStats(data.stats)}`;
    419                 break;
    420             }
    421             case 'error':
    422                 if (!currentVerified)
    423                     loaded = false;
    424                 finish(data.message, true);
    425                 break;
    426         }
    427     };
    428     current.onerror = event => { if (worker === current) {
    429         event.preventDefault();
    430         if (!currentVerified)
    431             loaded = false;
    432         finish(event.message || 'Worker failed.', true);
    433     } };
    434     send({ type: 'load', ...selection });
    435 }
    436 function selectExample(entry) {
    437     const name = examplePackage(entry);
    438     const latest = name ? packageReleases(releases, name)[0] : undefined;
    439     if (!latest || (latest.demo === entry.id && latest.hash === entry.hash))
    440         select(entry, latest);
    441     else
    442         loadBundle(latest);
    443 }
    444 function select(entry, release) {
    445     sourceAvailable = false;
    446     draft = false;
    447     scratch = false;
    448     originalSource = undefined;
    449     revealOutput.cancel();
    450     dispose();
    451     clearOutput();
    452     clearTreeExport();
    453     selected = entry;
    454     loaded = false;
    455     selection = { hash: entry.hash, localId: entry.id };
    456     hashInput.value = entry.hash;
    457     sourceEditor.hidden = true;
    458     el('source-notes').hidden = false;
    459     const pinned = release ?? releases.find(r => r.hash === entry.hash && r.demo === entry.id);
    460     chooseProgram(`demo:${entry.id}`, pinned?.version);
    461     showRelease(pinned);
    462     setPreset(entry);
    463     el('source').textContent = 'Verifying package…';
    464     newWorker(false);
    465 }
    466 function invalidateProgram() {
    467     el('bundle-download-status').textContent = '';
    468     sourceAvailable = false;
    469     draft = false;
    470     scratch = false;
    471     originalSource = undefined;
    472     revealOutput.cancel();
    473     dispose();
    474     clearOutput();
    475     clearTreeExport();
    476     loaded = false;
    477     selection = undefined;
    478     selected = undefined;
    479     preferredExport = undefined;
    480     exportsInput.replaceChildren();
    481     sourceEditor.hidden = true;
    482     el('source-notes').hidden = false;
    483     el('source').textContent = 'Load the selected program to inspect its source or package notes.';
    484     el('package-info').textContent = 'No bundle loaded.';
    485     showRelease();
    486     controls(false);
    487     el('status').textContent = 'Load package';
    488 }
    489 function loadBundle(release) {
    490     const value = release?.hash ?? hashInput.value;
    491     invalidateProgram();
    492     chooseProgram(release ? releaseProgram(release) : 'hash', release?.version);
    493     hashInput.value = value;
    494     try {
    495         const hash = bundleHash(value);
    496         selection = { hash };
    497         argsInput.value = '[]';
    498         argumentsUI.refresh(true);
    499         resultCodec.value = 'tricu';
    500         mode.value = 'pure';
    501         showRelease(release);
    502         el('source').textContent = 'Verifying registry package…';
    503         newWorker(false);
    504     }
    505     catch (error) {
    506         operation = 'loading';
    507         finish(error instanceof Error ? error.message : String(error), true);
    508     }
    509 }
    510 el('program-hash').onsubmit = event => { event.preventDefault(); loadBundle(); };
    511 hashInput.oninput = () => { chooseProgram('hash'); invalidateProgram(); };
    512 exportsInput.onchange = () => { markPreviousResult(); preferredName = exportsInput.value; clearTreeExport(); if (storedOutput())
    513     return; if (!scratch && selected?.presets?.[exportsInput.value])
    514     setPreset(selected, exportsInput.value);
    515 else
    516     presetExpression(); };
    517 runButton.onclick = () => {
    518     if (busy || runButton.disabled)
    519         return;
    520     revealOutput.arm();
    521     if (storedOutput()) {
    522         if (draft || !loaded || !selection)
    523             return;
    524         const request = { type: 'export', export: exportsInput.value, format: treeFormat(resultCodec.value) };
    525         if (worker)
    526             startExport(request, value => worker?.postMessage(value));
    527         else
    528             newWorker(false, true, request);
    529         return;
    530     }
    531     if (draft) {
    532         runDraft();
    533         return;
    534     }
    535     if (!loaded || !selection)
    536         return;
    537     operation = 'running';
    538     clearOutput();
    539     try {
    540         const args = guidedInputs.checked ? argumentsUI.read() : [], output = outputType(resultCodec.value);
    541         savedRun = { type: 'run', ...expressionInput(), export: exportsInput.value, arguments: args, output, mode: mode.value === 'io' ? 'io' : 'pure', step: steps.checked };
    542         if (worker) {
    543             controls(true);
    544             runClock();
    545             el('status').textContent = 'Running';
    546             worker.postMessage(savedRun);
    547         }
    548         else
    549             newWorker(true);
    550     }
    551     catch (error) {
    552         finish(error instanceof Error ? error.message : String(error), true);
    553     }
    554 };
    555 function resume(value) {
    556     if (!worker || !pending)
    557         return;
    558     const message = value === undefined ? { type: 'resume', id: pending.id } : { type: 'resume', id: pending.id, value };
    559     pending = undefined;
    560     inputForm.hidden = true;
    561     nextButton.disabled = true;
    562     el('status').textContent = 'Running';
    563     clock();
    564     worker.postMessage(message);
    565 }
    566 inputForm.onsubmit = event => {
    567     event.preventDefault();
    568     if (pending?.kind !== 'input')
    569         return;
    570     try {
    571         line(new Graph({ nodes: 16_000, fuel: 100_000 }), lineInput.value);
    572         el('result').textContent = '';
    573         el('result').className = '';
    574         resume(lineInput.value);
    575     }
    576     catch (error) {
    577         el('result').textContent = error instanceof Error ? error.message : String(error);
    578         el('result').className = 'error';
    579     }
    580 };
    581 nextButton.onclick = () => { if (pending?.kind === 'step')
    582     resume(); };
    583 stopButton.onclick = () => {
    584     imports.cancel();
    585     revealOutput.cancel();
    586     finish(draft ? 'Stopped. Runtime session discarded; compile the draft again to run it.' : 'Stopped. Session discarded.', true, 'cancelled');
    587 };
    588 el('stop-run').onclick = () => stopButton.click();
    589 el('reset').onclick = () => { if (busy)
    590     return; revealOutput.cancel(); clearOutput(); el('status').textContent = draft ? compiled ? 'Compiled' : 'Ready to compile' : loaded ? 'Ready' : 'Load package'; };
    591 shareButton.onclick = async () => {
    592     if (draft || !selection)
    593         return;
    594     const url = new URL(location.href), fragment = new URLSearchParams({ bundle: selection.hash, export: exportsInput.value });
    595     if (selection.localId)
    596         fragment.set('demo', selection.localId);
    597     url.hash = fragment.toString();
    598     try {
    599         await navigator.clipboard.writeText(url.href);
    600         el('bundle-download-status').textContent = 'Copied a link to this package and export.';
    601     }
    602     catch {
    603         el('bundle-download-status').textContent = url.href;
    604     }
    605 };
    606 window.addEventListener('pagehide', () => dispose());
    607 // Associate an example with its published name only through the catalogue's exact pin.
    608 function examplePackage(entry) {
    609     return releases.find(r => r.demo === entry.id && r.hash === entry.hash)?.name;
    610 }
    611 function programPackage(value) {
    612     if (value.startsWith('package:'))
    613         return value.slice(8);
    614     const entry = value.startsWith('demo:') ? entries.find(e => e.id === value.slice(5)) : undefined;
    615     return entry ? examplePackage(entry) : undefined;
    616 }
    617 function releaseProgram(release) {
    618     const entry = entries.find(e => e.id === release.demo && examplePackage(e) === release.name);
    619     return entry ? `demo:${entry.id}` : `package:${release.name}`;
    620 }
    621 function chooseProgram(value, version) {
    622     el('bundle-download-status').textContent = '';
    623     programPicker.value = value;
    624     const name = programPackage(value);
    625     el('program-release').hidden = name === undefined;
    626     el('package-toolbar').hidden = name === undefined && value !== 'hash';
    627     el('bundle-hash-help').hidden = value !== 'hash';
    628     el('program-hash').hidden = value !== 'hash';
    629     if (name && versionPackage !== name) {
    630         versionPackage = name;
    631         versionPicker.replaceChildren();
    632         for (const release of packageReleases(releases, name)) {
    633             const option = document.createElement('option');
    634             option.value = release.version;
    635             option.textContent = release.version;
    636             versionPicker.append(option);
    637         }
    638     }
    639     if (name)
    640         versionPicker.value = version ?? packageReleases(releases, name)[0].version;
    641 }
    642 function populatePrograms() {
    643     programPicker.replaceChildren();
    644     const placeholder = document.createElement('option');
    645     placeholder.value = '';
    646     placeholder.disabled = true;
    647     placeholder.textContent = 'Choose a program';
    648     programPicker.append(placeholder);
    649     for (const category of exampleGroups) {
    650         const group = document.createElement('optgroup');
    651         group.label = category;
    652         for (const entry of entries.filter(entry => exampleGroup(entry) === category)) {
    653             const option = document.createElement('option');
    654             option.value = `demo:${entry.id}`;
    655             option.textContent = entry.title;
    656             group.append(option);
    657         }
    658         if (group.children.length)
    659             programPicker.append(group);
    660     }
    661     const packages = document.createElement('optgroup');
    662     packages.label = 'Libraries';
    663     for (const release of selectorLibraries(releases)) {
    664         const option = document.createElement('option');
    665         option.value = `package:${release.name}`;
    666         option.textContent = libraryLabel(release.name);
    667         option.title = release.title;
    668         packages.append(option);
    669     }
    670     const hash = document.createElement('option');
    671     hash.value = 'hash';
    672     hash.textContent = 'Load by bundle hash…';
    673     programPicker.append(packages, hash);
    674     programPicker.disabled = false;
    675 }
    676 function showRelease(release) {
    677     el('module-import-help').hidden = release === undefined;
    678     el('module-import-source').textContent = release ? `!import ${release.manifest} as Library` : '';
    679     el('release-info').textContent = release ? `${release.title} · ${release.name}@${release.version} · module ${release.manifest}` : 'No named release selected. Direct bundle hashes pin the whole file.';
    680 }
    681 programPicker.onchange = () => {
    682     const value = programPicker.value;
    683     if (value.startsWith('demo:')) {
    684         const entry = entries.find(e => e.id === value.slice(5));
    685         if (entry)
    686             selectExample(entry);
    687     }
    688     else {
    689         chooseProgram(value);
    690         if (value === 'hash')
    691             hashInput.value = '';
    692         invalidateProgram();
    693         if (value.startsWith('package:'))
    694             loadSelectedRelease();
    695         else if (value === 'hash')
    696             hashInput.focus();
    697     }
    698 };
    699 versionPicker.onchange = invalidateProgram;
    700 el('load-release').onclick = loadSelectedRelease;
    701 function loadSelectedRelease() {
    702     const name = programPackage(programPicker.value);
    703     const release = releases.find(r => r.name === name && r.version === versionPicker.value);
    704     if (!release)
    705         return;
    706     const demo = entries.find(e => e.id === release.demo && e.hash === release.hash);
    707     if (demo)
    708         select(demo, release);
    709     else
    710         loadBundle(release);
    711 }
    712 function downloadHash() {
    713     const name = programPackage(programPicker.value);
    714     if (name)
    715         return releases.find(r => r.name === name && r.version === versionPicker.value)?.hash;
    716     if (programPicker.value === 'hash') {
    717         try {
    718             return bundleHash(hashInput.value);
    719         }
    720         catch {
    721             return undefined;
    722         }
    723     }
    724     return undefined;
    725 }
    726 downloadButton.onclick = () => {
    727     const hash = downloadHash();
    728     if (busy || draft || !hash)
    729         return;
    730     // Download has its own verified target, not the loaded executable selection.
    731     // It never changes source, definitions, arguments or the Run/Export target.
    732     dispose();
    733     operation = 'downloading';
    734     const current = new Worker(workerURL(), { type: 'module' });
    735     worker = current;
    736     controls(true);
    737     el('status').textContent = 'Downloading bundle';
    738     el('bundle-download-status').textContent = 'Verifying published bundle…';
    739     el('bundle-download-status').classList.remove('error');
    740     deadline.reset(PACKAGE_LOAD_MS);
    741     clock();
    742     current.onmessage = ({ data }) => {
    743         if (worker !== current)
    744             return;
    745         if (data.type === 'loaded') {
    746             if (data.hash !== hash) {
    747                 finish('Bundle download target mismatch', true);
    748                 return;
    749             }
    750             current.postMessage({ type: 'download' });
    751         }
    752         else if (data.type === 'downloaded') {
    753             if (data.hash !== hash) {
    754                 finish('Bundle download target mismatch', true);
    755                 return;
    756             }
    757             const url = URL.createObjectURL(new Blob([new Uint8Array(data.bytes)], { type: 'application/vnd.arboricx.bundle' }));
    758             const anchor = document.createElement('a');
    759             anchor.href = url;
    760             anchor.download = `${hash}.arboricx`;
    761             document.body.append(anchor);
    762             anchor.click();
    763             anchor.remove();
    764             setTimeout(() => URL.revokeObjectURL(url), 1000);
    765             const message = `Downloaded original verified bundle ${hash}. No draft or applied result included.`;
    766             dispose();
    767             el('status').textContent = loaded ? 'Ready' : 'Load package';
    768             el('bundle-download-status').textContent = message;
    769         }
    770         else if (data.type === 'error')
    771             finish(data.message, true);
    772     };
    773     current.onerror = event => { if (worker === current)
    774         finish(event.message || 'Worker error', true); };
    775     current.postMessage({ type: 'load', hash });
    776 };
    777 resultCodec.onchange = markPreviousResult;
    778 argsInput.addEventListener('input', markPreviousResult);
    779 el('argument-fields').addEventListener('input', markPreviousResult);
    780 mode.onchange = () => { markPreviousResult(); el('io-console').hidden = storedOutput() || mode.value !== 'io' && !el('terminal').textContent; };
    781 expressionEditor.oninput = markPreviousResult;
    782 guidedInputs.onchange = () => {
    783     if (!guidedInputs.checked) {
    784         try {
    785             presetExpression();
    786         }
    787         catch (error) {
    788             guidedInputs.checked = true;
    789             outputUI.clear();
    790             el('result').textContent = String(error);
    791         }
    792     }
    793     markPreviousResult();
    794     controls(busy);
    795 };
    796 sourceEditor.addEventListener('compositionstart', () => { composing = true; cancelIdleCompile(); });
    797 sourceEditor.addEventListener('compositionend', () => { composing = false; if (draft)
    798     queueCompile(); });
    799 sourceEditor.oninput = () => {
    800     if (busy && operation !== 'compiling')
    801         return;
    802     editRevision++;
    803     markPreviousResult();
    804     el('compile-diagnostic').hidden = true;
    805     if (operation === 'compiling') {
    806         draft = true;
    807         controls(true);
    808         queueCompile();
    809         return;
    810     }
    811     preferredName = exportsInput.value || preferredName;
    812     keepDraft();
    813     clearTreeExport();
    814     if (!compiled)
    815         exportsInput.replaceChildren();
    816     draft = compiled || scratch || sourceEditor.value !== originalSource;
    817     controls(false);
    818     if (!draft && selection) {
    819         preferredExport = preferredName;
    820         newWorker(false, true);
    821     }
    822     else {
    823         el('status').textContent = 'Changes pending';
    824         queueCompile();
    825     }
    826 };
    827 restoreButton.onclick = () => {
    828     if (originalSource === undefined)
    829         return;
    830     dispose();
    831     sourceEditor.value = originalSource;
    832     sourceEditor.dispatchEvent(new Event('input'));
    833     controls(busy);
    834 };
    835 compileButton.onclick = () => compileDraft();
    836 function compileDraft() {
    837     if (!draft || busy)
    838         return;
    839     const snapshot = { revision: editRevision, source: sourceEditor.value };
    840     dispose();
    841     operation = 'compiling';
    842     markPreviousResult();
    843     clearTreeExport();
    844     el('compile-diagnostic').hidden = true;
    845     try {
    846         sourceText(sourceEditor.value);
    847         const current = draftWorker(snapshot);
    848         controls(true);
    849         deadline.reset(Infinity);
    850         clock();
    851         el('status').textContent = 'Compiling';
    852         el('compiler-stats').textContent = 'Loading pinned compiler and source dependencies…';
    853         const started = performance.now();
    854         const activity = () => { el('compile-activity').hidden = false; el('compile-activity').textContent = `Compiling trees locally · ${Math.floor((performance.now() - started) / 1000)} seconds elapsed · Stop is available`; };
    855         activity();
    856         compilerTimer = setInterval(activity, 1000);
    857         current.postMessage({ type: 'compile', source: snapshot.source, localModules: localModules.inputs(),
    858             ...(!scratch && selected?.environment !== undefined ? { environment: selected.environment } : {}) });
    859     }
    860     catch (error) {
    861         finish(error instanceof Error ? error.message : String(error), true);
    862     }
    863 }
    864 function draftWorker(snapshot) {
    865     const current = new Worker(workerURL(), { type: 'module' });
    866     worker = current;
    867     const discardStale = () => {
    868         if (operation !== 'compiling' || snapshot.revision === editRevision)
    869             return false;
    870         dispose();
    871         el('status').textContent = 'Changes pending';
    872         queueCompile(Math.max(0, idleDue - performance.now()));
    873         return true;
    874     };
    875     current.onmessage = ({ data }) => {
    876         if (worker !== current)
    877             return;
    878         if ((data.type === 'compiled' || data.type === 'error') && discardStale())
    879             return;
    880         switch (data.type) {
    881             case 'module-exported': {
    882                 idleDraft();
    883                 const url = URL.createObjectURL(new Blob([new Uint8Array(data.bytes).buffer], { type: 'application/octet-stream' }));
    884                 const anchor = document.createElement('a');
    885                 anchor.href = url;
    886                 anchor.download = `${data.hash}.arboricx`;
    887                 anchor.click();
    888                 URL.revokeObjectURL(url);
    889                 el('compiled-module-status').textContent = `Exported compiled module. Manifest: ${data.manifest} · Bundle SHA-256: ${data.hash}. Open the downloaded file under Local modules to import it.`;
    890                 el('status').textContent = 'Compiled';
    891                 break;
    892             }
    893             case 'module-export-error':
    894                 idleDraft();
    895                 el('compiled-module-status').textContent = data.message;
    896                 el('status').textContent = 'Module export failed';
    897                 break;
    898             case 'compiled':
    899                 stopCompilerActivity();
    900                 pauseClock();
    901                 operation = undefined;
    902                 compiledSource = snapshot.source;
    903                 compiledRevision = snapshot.revision;
    904                 compiled = true;
    905                 exportsInput.replaceChildren();
    906                 for (const name of data.names) {
    907                     const option = document.createElement('option');
    908                     option.value = name;
    909                     option.textContent = name;
    910                     exportsInput.append(option);
    911                 }
    912                 if (data.names.includes(preferredName))
    913                     exportsInput.value = preferredName;
    914                 idleDraft();
    915                 el('status').textContent = 'Compiled';
    916                 compilationStats = `${data.names.length} definition(s) · ${Math.round(data.milliseconds)} ms · ${runtimeStats(data.stats)}`;
    917                 el('compiler-stats').textContent = compilationStats;
    918                 break;
    919             case 'state':
    920                 if (data.state === 'running') {
    921                     clock();
    922                     pending = undefined;
    923                     el('status').textContent = operation === 'compiling' ? 'Compiling' : operation === 'exporting' ? 'Exporting module' : 'Running';
    924                 }
    925                 else {
    926                     pauseClock();
    927                     pending = { id: data.id, kind: data.state };
    928                     inputForm.hidden = data.state !== 'input';
    929                     nextButton.disabled = data.state !== 'step';
    930                     el('status').textContent = data.state === 'input' ? 'Waiting for input' : 'Paused before action';
    931                     revealOutput.reveal(data.state === 'input' ? inputForm : nextButton);
    932                 }
    933                 break;
    934             case 'trace': {
    935                 const row = document.createElement('li');
    936                 row.textContent = data.phase === 'before' ? `${data.event.step} ${data.event.name}: ${data.event.detail}` : `→ ${data.event.value}`;
    937                 el('trace').append(row);
    938                 el('trace-empty').hidden = true;
    939                 break;
    940             }
    941             case 'output':
    942                 el('terminal').textContent += data.text;
    943                 revealOutput.reveal(el('io-console'));
    944                 break;
    945             case 'done':
    946                 idleDraft();
    947                 el('status').textContent = 'Finished';
    948                 outputUI.result(data.result, `Evaluated local draft · ${backends[selectedBackend].label}`);
    949                 revealOutput.reveal(el('result'));
    950                 el('compiler-stats').textContent = `${compilationStats} · ${runtimeStats(data.stats)}`;
    951                 break;
    952             case 'error':
    953                 if (operation === 'running' && !data.fatal) {
    954                     idleDraft();
    955                     outputUI.clear();
    956                     el('status').textContent = 'Stopped';
    957                     el('result').textContent = operationFailure('running', data.message);
    958                     el('result').className = 'error';
    959                     revealOutput.reveal(el('result'));
    960                 }
    961                 else
    962                     finish(data.message, true);
    963                 break;
    964         }
    965     };
    966     current.onerror = event => { if (worker === current) {
    967         event.preventDefault();
    968         if (!discardStale())
    969             finish(event.message || 'Compiler Worker failed.', true);
    970     } };
    971     return current;
    972 }
    973 // Definitions exist only in the owning live Worker; stopping discards them.
    974 function idleDraft() {
    975     stopCompilerActivity();
    976     pauseClock();
    977     operation = undefined;
    978     pending = undefined;
    979     inputForm.hidden = true;
    980     nextButton.disabled = true;
    981     controls(false);
    982 }
    983 function keepDraft() {
    984     if (worker && compiled && !busy)
    985         idleDraft();
    986     else
    987         dispose();
    988 }
    989 function runDraft() {
    990     if (!compiled || busy || sourceEditor.value !== compiledSource || editRevision !== compiledRevision)
    991         return;
    992     try {
    993         const args = guidedInputs.checked ? argumentsUI.read() : [], output = outputType(resultCodec.value);
    994         const expression = expressionInput();
    995         if (!worker || busy)
    996             return;
    997         operation = 'running';
    998         clearOutput();
    999         controls(true);
   1000         runClock();
   1001         el('status').textContent = 'Running';
   1002         const current = worker;
   1003         current.postMessage({ type: 'run', ...expression, export: exportsInput.value, arguments: args, output, mode: mode.value, step: steps.checked });
   1004     }
   1005     catch (error) {
   1006         outputUI.clear();
   1007         el('status').textContent = 'Stopped';
   1008         el('result').textContent = operationFailure('running', error instanceof Error ? error.message : String(error));
   1009         el('result').className = 'error';
   1010         revealOutput.reveal(el('result'));
   1011         keepDraft();
   1012     }
   1013 }
   1014 backendPicker.onchange = () => {
   1015     selectedBackend = backend(backendPicker.value);
   1016     editRevision++;
   1017     dispose();
   1018     markPreviousResult();
   1019     if (draft) {
   1020         controls(false);
   1021         el('status').textContent = 'Changes pending';
   1022         queueCompile(0);
   1023     }
   1024     else if (selection)
   1025         newWorker(false, true);
   1026 };
   1027 const examples = {
   1028     identity: { name: 'id', source: 'id = (x : x)', args: [{ type: 'number', value: '42' }], output: 'number', mode: 'pure' },
   1029     contracts: { name: 'id', source: 'id x =@nat? x', args: [{ type: 'number', value: '42' }], output: 'number', mode: 'pure' },
   1030     pair: { name: 'pair', source: 'pair a b = t a b', args: [{ type: 'number', value: '7' }, { type: 'string', value: 'hi' }], output: 'tree', mode: 'pure' },
   1031     definitions: { name: 'answer', source: 'id = (x : x)\nanswer = id 42', args: [], output: 'number', mode: 'pure' },
   1032     input: { name: 'io', source: 'io=t "tricuIO" (t 1 (t 11 t))', args: [], output: 'string', mode: 'io' },
   1033 };
   1034 for (const button of document.querySelectorAll('[data-compiler-example]'))
   1035     button.onclick = () => {
   1036         const math = packageReleases(releases, 'tricu-lib-native-math')[0];
   1037         const example = button.dataset['compilerExample'] === 'module' && math
   1038             ? { name: 'answer', source: `!import ${math.manifest} as N\n!export answer\n\nanswer = N.add 40 2`, args: [], output: 'number', mode: 'pure' }
   1039             : examples[button.dataset['compilerExample']];
   1040         if (!example)
   1041             return;
   1042         chooseProgram('');
   1043         revealOutput.cancel();
   1044         keepDraft();
   1045         clearOutput();
   1046         clearTreeExport();
   1047         scratch = true;
   1048         draft = true;
   1049         originalSource = undefined;
   1050         if (!compiled)
   1051             exportsInput.replaceChildren();
   1052         editRevision++;
   1053         preferredName = example.name;
   1054         sourceEditor.hidden = false;
   1055         el('source-notes').hidden = true;
   1056         sourceEditor.value = example.source;
   1057         argsInput.value = JSON.stringify(example.args, null, 2);
   1058         argumentsUI.refresh(true);
   1059         resultCodec.value = visibleCodec(example.output);
   1060         mode.value = example.mode;
   1061         presetExpression(example.name);
   1062         controls(false);
   1063         el('package-info').textContent = 'Scratch program · no published bundle selected.';
   1064         showRelease();
   1065         el('source-label').textContent = 'Scratch program · explicit modules or contract helpers';
   1066         el('status').textContent = 'Changes pending';
   1067         queueCompile();
   1068     };
   1069 function loadFragment() {
   1070     const fragment = new URLSearchParams(location.hash.slice(1));
   1071     const requested = fragment.get('bundle');
   1072     if (!requested)
   1073         return false;
   1074     const hash = bundleHash(requested), local = entries.find(e => e.id === fragment.get('demo') && e.hash === hash);
   1075     preferredExport = fragment.get('export') ?? undefined;
   1076     if (local)
   1077         select(local);
   1078     else {
   1079         chooseProgram('hash');
   1080         showRelease();
   1081         draft = false;
   1082         scratch = false;
   1083         originalSource = undefined;
   1084         sourceAvailable = false;
   1085         revealOutput.cancel();
   1086         dispose();
   1087         clearOutput();
   1088         clearTreeExport();
   1089         loaded = false;
   1090         selected = undefined;
   1091         argsInput.value = '[]';
   1092         argumentsUI.refresh(true);
   1093         resultCodec.value = 'tricu';
   1094         mode.value = 'pure';
   1095         selection = { hash };
   1096         hashInput.value = hash;
   1097         newWorker(false);
   1098     }
   1099     return true;
   1100 }
   1101 window.addEventListener('hashchange', () => {
   1102     if (!entries.length)
   1103         return;
   1104     try {
   1105         if (new URLSearchParams(location.hash.slice(1)).has('bundle'))
   1106             loadFragment();
   1107     }
   1108     catch (error) {
   1109         loaded = false;
   1110         finish(error instanceof Error ? error.message : String(error), true);
   1111     }
   1112 });
   1113 let initialized = false;
   1114 async function initializePackages() {
   1115     if (initialized)
   1116         return;
   1117     initialized = true;
   1118     operation = 'catalogue';
   1119     try {
   1120         if (!globalThis.Worker || !globalThis.crypto?.subtle)
   1121             throw new Error('Playground needs Workers and a secure context (HTTPS or localhost).');
   1122         const bytes = await fetchBytes(new URL('../catalogue.json', import.meta.url), 64_000);
   1123         const input = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
   1124         if (!Array.isArray(input) || input.length > 32)
   1125             throw new Error('Invalid catalogue');
   1126         entries = input.map((raw) => {
   1127             if (!raw || typeof raw !== 'object')
   1128                 throw new Error('Invalid catalogue entry');
   1129             const v = raw;
   1130             for (const key of ['id', 'title', 'source', 'sourceSha256', 'export'])
   1131                 if (typeof v[key] !== 'string')
   1132                     throw new Error('Invalid catalogue field');
   1133             const hash = bundleHash(v['hash']);
   1134             literals(v['arguments']);
   1135             outputType(v['output']);
   1136             if (v['mode'] !== 'pure' && v['mode'] !== 'io')
   1137                 throw new Error('Invalid catalogue mode');
   1138             if (v['category'] !== undefined && !exampleGroups.includes(v['category']))
   1139                 throw new Error('Invalid example category');
   1140             if (v['environment'] !== undefined && (typeof v['environment'] !== 'string' || v['environment'].length > 128))
   1141                 throw new Error('Invalid demo environment');
   1142             return { ...v, hash };
   1143         });
   1144         const indexBytes = await fetchBytes(new URL('../packages.json', import.meta.url), 1_048_576);
   1145         releases = packageIndex(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(indexBytes)));
   1146         populatePrograms();
   1147         if (selection || draft) {
   1148             operation = undefined;
   1149             return;
   1150         }
   1151         if (!loadFragment()) {
   1152             const initial = entries.find(e => e.id === 'names-values') ?? entries[0];
   1153             if (!initial)
   1154                 throw new Error('Empty catalogue');
   1155             selectExample(initial);
   1156         }
   1157     }
   1158     catch (error) {
   1159         initialized = false;
   1160         loaded = false;
   1161         finish(error instanceof Error ? error.message : String(error), true);
   1162     }
   1163 }
   1164 setupInspector();
   1165 void initializePackages();