diff --git a/docs/design/0160-static-title-screen.md b/docs/design/0160-static-title-screen.md
new file mode 100644
index 00000000..53fbcf2b
--- /dev/null
+++ b/docs/design/0160-static-title-screen.md
@@ -0,0 +1,231 @@
+---
+title: "DX-0160 - Static title screen only"
+legend: "DX"
+lane: "design"
+issue: "https://github.com/flyingrobots/jedit/issues/338"
+status: "active"
+owners:
+ - "@flyingrobots"
+created: "2026-10-14"
+updated: "2026-10-14"
+---
+
+# DX-0160 - Static title screen only
+
+## Linked Issue
+
+- https://github.com/flyingrobots/jedit/issues/338
+
+## Decision Summary
+
+Remove the ray tracer from the live title-screen integration. The static Jim
+logo is the only no-buffer workspace body. Former camera, shader, material,
+mouse-look, and scene-picker actions cannot switch it to an animated scene.
+Standalone preview, profile, and recording tools retain their existing engine.
+
+## Sponsored Human
+
+A keyboard user wants to use the title screen without accidentally activating
+ray tracing by pressing Space or moving the pointer.
+
+## Sponsored Agent
+
+An agent needs a production module graph and runtime witnesses proving that
+ray tracing is unreachable from the workspace.
+
+## Hill
+
+Former title-scene input leaves the static logo intact, schedules no scene
+work, and does not create camera state or reenable idle animation.
+
+## Current Truth
+
+Space is interpreted as camera jump, and any camera update selects the legacy
+ray-traced backdrop. Ctrl+L, shader keys, and mouse look can also activate it.
+The renderer accepts legacy scene state and injected ray-tracer renderers.
+See [title-screen-key-bindings.ts#154:ecefbbb695ddeedaed94e0046c580e2041cca40f](https://github.com/flyingrobots/jedit/blob/ecefbbb695ddeedaed94e0046c580e2041cca40f/src/app/workspace/title-screen-key-bindings.ts#L154)
+and [viewer-content.ts#135:ecefbbb695ddeedaed94e0046c580e2041cca40f](https://github.com/flyingrobots/jedit/blob/ecefbbb695ddeedaed94e0046c580e2041cca40f/src/app/workspace/viewer-content.ts#L135).
+
+## Problem
+
+Ordinary input can unexpectedly replace the static logo and start ray tracing.
+Changing one shortcut leaves other activation paths reachable.
+
+## Scope
+
+- Remove live ray-tracer rendering, caches, controls, scene picker, and title-only stats.
+- Remove scene/camera state, messages, loader composition, and advertised bindings.
+- Preserve the static logo, file browsing, Settings, editor, quit flow, and generic instrumentation.
+
+## Non-Goals
+
+- Delete or redesign the standalone engine, scene assets, preview/profile/record tools.
+- Change text authority, Leader settings, or implement leader dispatch.
+
+## User Experience / Product Shape
+
+The no-buffer view always shows the existing static Jim logo. Space, WASD,
+arrow-camera controls, number/shader keys, material keys, and mouse movement
+perform no title-scene action. Ctrl+L no longer opens a scene picker. Existing
+file, Settings, command-line, and quit actions retain their focus ownership.
+Help and footer stop advertising scene selection. There is no new layout,
+transition, input capture, or persistent preference.
+
+### User Journey
+
+Start Jim → see static logo → open Settings or a file, or quit. Former scene
+input cannot change that view. Closing the last buffer returns to the logo.
+
+### Wide UI Mockup
+
+Retain the existing logo at 100×32; no visual redesign. The footer lists only
+currently available workspace actions. Actual rendered output is verified.
+
+### Narrow UI Mockup
+
+Retain the existing logo and drawer behavior at 60×16. Below the existing
+60×12 minimum, the small-terminal notice retains precedence.
+
+### Accessibility Considerations
+
+Remove obsolete help. Existing text labels, theme, keyboard navigation,
+Settings, file-drawer focus, and quit confirmation remain available.
+
+## Runtime / API Contract
+
+The viewer no longer accepts or invokes a ray-tracer renderer. Workspace
+initialization no longer builds scene/camera state. The runtime no longer
+accepts scene-load results or camera animation messages. A stale external scene
+message cannot mutate the workspace. Title Escape still opens quit confirmation.
+Standalone renderer APIs remain separate from production workspace composition.
+
+## Lower Modes
+
+The static logo and ordinary help remain available in pipe/no-color rendering.
+Small-terminal notice behavior remains unchanged. No optional scene loader,
+mesh, camera, or ray tracer is required for startup or title input.
+
+## Data / State Model
+
+| Category | Description |
+| --- | --- |
+| Source of truth | Existing static artwork and workspace/editor state. |
+| Derived state | Logo surface sized to the viewer viewport. |
+| Removed state | Scene, meshes, camera, material/shader selection, scene-picker focus, and tracing caches. |
+| Reset behavior | Return to the logo when no buffer is displayed. |
+| Persistence | No configuration or Echo contract changes. |
+
+## Accessibility Posture
+
+No new interaction or color-only state. Escape, Settings, and file browsing
+remain keyboard reachable. Mouse movement is inert on the logo.
+
+## Localization / Directionality Posture
+
+Remove obsolete scene-picker hints from help, footer, and catalogs. Retain
+existing localized labels and LTR/RTL logo presentation. Regenerate catalogs.
+
+## Agent Inspectability / Explainability Posture
+
+Runtime tests compare rendered surfaces and emitted commands. A production
+module-loading witness verifies that no ray-tracer renderer or scene loader is
+reachable through the live workspace dependency graph.
+
+## Linked Invariants
+
+- Tests exercise runtime and rendered output, not design-document assertions.
+- Preserve editor behavior and the Echo authority boundary.
+- Use the existing `title-unfreeze` label under the
+ [freeze policy](../method/backlog/leash/title-scene-freeze.md). The human's
+ explicit removal request authorizes this narrow title integration change;
+ engine/asset polish remains frozen.
+
+## Design Alternatives Considered
+
+Only removing Space leaves mouse look, shader controls, and scene selection
+reachable. Hiding the scene UI retains dormant production tracing and state.
+Deleting the standalone engine expands the request beyond the live title screen.
+
+## Decision
+
+Remove the complete live integration and retain the isolated standalone tools.
+
+## Implementation Slices
+
+- [x] Slice 1: Remove live rendering and title-scene input paths.
+- [x] Slice 2: Remove scene state/dependencies/help and verify preserved workspace flows.
+
+## Tests To Write First
+
+- [x] Former keyboard and mouse controls leave the logo unchanged and emit no scene work.
+- [x] Startup and stale scene/camera messages cannot create ray-tracer state.
+- [x] Logo remains idle and renders even when legacy properties are supplied.
+- [x] Settings, file drawer, quit, minimum geometry, and standalone tools retain behavior.
+
+## Acceptance Criteria
+
+- [x] All live title ray-tracer paths are removed.
+- [x] Real runtime/render/module-graph witnesses pass.
+- [x] Obsolete integration tests are replaced by static-title regressions.
+- [x] Full repository validation passes and CI is reported at the published head.
+
+## Validation Plan
+
+```bash
+npm run check
+npm run ci:verify-shards
+```
+
+## Playback / Witness
+
+Run Jim at 100×32 and 60×16. Press Space, WASD, 1, 2, period, M, Ctrl+L,
+and move the pointer. The logo remains. F2 opens Settings; Ctrl+B opens Files;
+Escape opens quit confirmation. After startup completion, an idle time tick
+retains the same model reference.
+
+## Risks
+
+The legacy title spec mixes removed scene behavior with current file-drawer
+behavior. Remove only obsolete assertions and keep the live file/Settings/quit
+witnesses. Standalone tools share the engine; do not delete their dependencies.
+
+## Follow-On Debt
+
+None introduced. Reintroducing a title ray tracer would require a separate
+explicit product decision and new runtime integration.
+
+## Retrospective
+
+The live integration is removed, including title camera/shader/material input,
+mouse look, scene picker, scene/camera state and messages, scene-loader
+composition, renderer injection, caches/governor, and title-scene stats. The
+static logo is the only no-buffer body. The standalone engine, assets, and
+preview/profile/record tools are unchanged.
+
+Five of the six new runtime/render/module-loading witnesses failed before
+removal; the preserved workspace-action witness also passed on the baseline.
+All six passed afterward. They cover former inputs, pointer movement, stale messages,
+legacy property injection, preserved Settings/Files/quit actions, and the
+production dependency graph. The real graph no longer loads `ui/title-screen`,
+`ui/title-scene`, `app/title-camera-session`, or `adapters/title-scene-loader`.
+Obsolete integration tests were retired; current workspace-flow and standalone
+engine/tool tests remain. Unused old title-test helpers were removed.
+
+`npm run check` passed: 830 JavaScript tests passed, 12 existing skips, 99 native
+Rust tests passed, and no quality regressions. The final focused title tests
+passed 25/25 after removing unused helpers. Test-shard coverage passed with
+130 spec files. The test-file size ratchet was lowered to the new bounds for
+workspace command-line, runtime, and title-screen specs.
+
+Live PTY verification used the real native Echo host with an isolated WAL at
+100×32 and 60×16. At each size, 17 former keyboard/mouse activation events
+produced one unchanged rendered-cell digest, zero commands, and no scene or
+camera properties. At 100×32 F2 opened/closed Settings and Ctrl+B opened Files.
+At 60×16 Escape opened quit confirmation, and confirmation exited cleanly.
+Rendered static-logo cells were visually inspected. No text mutation was needed
+for this presentation change, and no Echo API or authority boundary changed.
+
+PR: https://github.com/flyingrobots/jedit/pull/339
+
+No follow-on work is required for the removal. The separate Leader-settings
+PR #336 and its unresolved configuration-ownership question are unchanged.
diff --git a/docs/topics/ui/README.md b/docs/topics/ui/README.md
index dc517ef2..ed5410fe 100644
--- a/docs/topics/ui/README.md
+++ b/docs/topics/ui/README.md
@@ -25,7 +25,7 @@ Jim UI documents use three terminal width profiles:
| Page | Surface |
| --- | --- |
-| [Title Screen](title-screen.md) | Startup scene, title browser, and first file-open path. |
+| [Title Screen](title-screen.md) | Static logo, workspace actions, and first file-open path. |
| [Editor Chrome](editor-chrome.md) | Source viewport, gutter, status footer, dirty state, and cursor position. |
| [Settings Menu](settings-menu.md) | F2 settings drawer, keyboard controls, diagnostics, and change feedback. |
| [Command Line And Completions](command-line-and-completions.md) | Normal-mode `:` input, command help, file completion, and inline suggestions. |
diff --git a/docs/topics/ui/title-screen.md b/docs/topics/ui/title-screen.md
index 08be08b9..2719cee6 100644
--- a/docs/topics/ui/title-screen.md
+++ b/docs/topics/ui/title-screen.md
@@ -1,9 +1,7 @@
# Title Screen
-The title screen is the first Jim surface when no file has been opened. It is
-both a product identity surface and a usable file-opening surface.
-
-
+The title screen is the first Jim surface when no file has been opened. It
+shows the static Jim logo and ordinary workspace chrome.
## How To Open It
@@ -13,52 +11,60 @@ Start Jim without a file argument:
npm run dev
```
-In an installed build, launch the Jim executable without a path. The title
-screen remains active until a file or workspace entry is opened.
+In a built checkout, use `npm start`. The no-buffer view remains until a file
+is opened and returns when no buffer is displayed.
## What It Shows
-The current title screen renders a ray-traced scene in terminal cells. The
-scene is decorative, but it is still a live Jim screen and must obey the same
-chrome rules as the editor:
-
-- the active title appears in the top chrome;
-- performance and scene details can appear in debug overlays;
-- footer hints describe available navigation and command shortcuts;
-- file opening should route through the same production text path as later
- editor commands.
+The body renders committed logo artwork sized to the available viewport. It
+has no live ray tracing, scene selection, camera controls, shader/material
+selection, mouse look, or title-scene performance panel. Startup does not load
+meshes or a scene loader. Once startup completes, an idle tick preserves the
+model reference and does not request another render.
-The title renderer is intentionally separate from source editing. It must not
-own text authority, file authority, or settings authority.
+The title row, footer, file drawer, Settings, theme selection, and generic
+performance instrumentation retain their existing behavior. The title renderer
+has no text authority. File opening uses the production Echo-backed text session.
## Keyboard Use
-Common title-screen actions:
-
| Key | Action |
| --- | --- |
-| `:` | Open Normal-mode command line. |
-| `ctrl+b` | Open the files drawer. |
-| `ctrl+g` | Open the Graft drawer when available. |
-| `ctrl+t` | Cycle theme. |
-| `F2` | Open settings. |
-
-The exact footer text is the runtime hint source. If this page and the footer
-disagree, the footer is more likely to match the running build.
+| `:` | Open the command line. |
+| `Ctrl+B` | Toggle the files drawer. |
+| `Ctrl+G` | Toggle the Graft drawer when available. |
+| `Ctrl+T` | Cycle theme. |
+| `F2` | Toggle Settings. |
+| `Esc` or `q` | Open quit confirmation. |
+
+Space, WASD, arrow keys, `1`, `2`, `.`, `m`, and `M` have no camera or shader
+meaning on the title screen. `Ctrl+L` no longer opens a scene picker. Pointer
+movement does not change the logo. Focused drawers and the command line retain
+ownership of their own keys.
## File Opening
-From the title screen, `:edit ` and file completion should use the same
-workspace file-opening behavior as the files drawer. Selecting a directory must
-descend into that directory. Selecting a file must open it through the
-production Echo-backed text session.
+`:edit ` and file completion use the same workspace file-opening behavior
+as the files drawer. Selecting a directory descends into it. Selecting a file
+opens it through the production text session.
+
+## Small Terminals
+
+At 60×16, the logo fits the viewer and drawers retain their existing scrolling
+behavior. Below the 60×12 minimum, Jim shows its existing size notice.
+
+## Standalone Scene Tools
+
+The ray-tracer engine and scene assets remain available to the explicit
+`title:preview`, `title:profile`, and `title:record` tools. They are separate
+from the live workspace; no title-screen action loads them.
## Implementation Map
| File | Responsibility |
| --- | --- |
-| [`src/ui/title-screen.ts`](../../../src/ui/title-screen.ts) | Title scene surface rendering. |
-| [`src/ui/title-scene.ts`](../../../src/ui/title-scene.ts) | Ray-traced title scene rendering. |
-| [`src/app/title-camera-session.ts`](../../../src/app/title-camera-session.ts) | Title camera interaction state. |
-| [`src/app/workspace/title-screen-key-bindings.ts`](../../../src/app/workspace/title-screen-key-bindings.ts) | Title-screen key routing. |
-| [`src/app/workspace/file-tree.ts`](../../../src/app/workspace/file-tree.ts) | Shared file entry open behavior. |
+| [`src/ui/jim-logo-screen.ts`](../../../src/ui/jim-logo-screen.ts) | Static logo artwork rendering and viewport fit. |
+| [`src/app/workspace/viewer-content.ts`](../../../src/app/workspace/viewer-content.ts) | Select static logo, source, or Markdown preview. |
+| [`src/app/workspace/title-screen-key-bindings.ts`](../../../src/app/workspace/title-screen-key-bindings.ts) | No-buffer Escape quit-confirmation routing. |
+| [`src/app/workspace/file-tree.ts`](../../../src/app/workspace/file-tree.ts) | Shared file entry opening. |
+| [`spec/static-title-screen.spec.mjs`](../../../spec/static-title-screen.spec.mjs) | Input, rendering, stale-message, idle, and module-loading witnesses. |
diff --git a/quality-baseline.json b/quality-baseline.json
index 987bd78a..101c833e 100644
--- a/quality-baseline.json
+++ b/quality-baseline.json
@@ -7,10 +7,10 @@
"spec/workspace-command-completion.spec.mjs": 932,
"spec/workspace-command-line.spec.mjs": 1974,
"spec/workspace-helpers.mjs": 623,
- "spec/workspace-runtime.spec.mjs": 619,
+ "spec/workspace-runtime.spec.mjs": 520,
"spec/workspace-text-boundaries.spec.mjs": 559,
"spec/workspace-text-cutover.spec.mjs": 2045,
- "spec/workspace-title-screen.spec.mjs": 1184,
+ "spec/workspace-title-screen.spec.mjs": 730,
"tests/replace-range-law-cycle.spec.mjs": 539
},
"forbiddenTypeKeywords": {},
diff --git a/spec/help.spec.mjs b/spec/help.spec.mjs
index a7c40ff9..54956c40 100644
--- a/spec/help.spec.mjs
+++ b/spec/help.spec.mjs
@@ -12,9 +12,10 @@ async function loadHelpModule() {
return import(pathToFileURL(HELP_PATH).href);
}
-test('help text scopes scene picker shortcut to the title screen', async () => {
+test('help advertises current workspace actions without a scene picker', async () => {
const help = await loadHelpModule();
const overlay = help.createEditorHelpOverlay(120, 40);
- assert.match(overlay.content, /ctrl\+l\s+open scene picker \(title screen only\)/);
+ assert.doesNotMatch(overlay.content, /ctrl\+l|scene picker/i);
+ assert.match(overlay.content, /f2\s+open or close settings/);
});
diff --git a/spec/keybindings.spec.mjs b/spec/keybindings.spec.mjs
index 37a25eba..618226ca 100644
--- a/spec/keybindings.spec.mjs
+++ b/spec/keybindings.spec.mjs
@@ -38,15 +38,3 @@ test('settings and markdown preview use different function keys', async () => {
assert.equal(keybindings.JEDIT_MARKDOWN_PREVIEW_TOGGLE_KEY, 'f3');
assert.notEqual(keybindings.JEDIT_SETTINGS_TOGGLE_KEY, keybindings.JEDIT_MARKDOWN_PREVIEW_TOGGLE_KEY);
});
-
-test('scene picker label is derived from the key binding metadata', async () => {
- const keybindings = await loadKeybindingsModule();
-
- assert.equal(
- keybindings.JEDIT_SCENE_PICKER_TOGGLE_LABEL,
- keybindings.formatJeditKeyLabel({
- key: keybindings.JEDIT_SCENE_PICKER_TOGGLE_KEY,
- ctrl: true,
- }),
- );
-});
diff --git a/spec/static-title-screen.spec.mjs b/spec/static-title-screen.spec.mjs
new file mode 100644
index 00000000..d34956ca
--- /dev/null
+++ b/spec/static-title-screen.spec.mjs
@@ -0,0 +1,151 @@
+import assert from 'node:assert/strict';
+import { mkdtempSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import path from 'node:path';
+import { spawnSync } from 'node:child_process';
+import test from 'node:test';
+import { pathToFileURL } from 'node:url';
+import { importDist, mockRuntime, mockJeditTheme, REPO_ROOT } from './workspace-helpers.mjs';
+
+// Ray-tracing runtime modules the live workspace must not reach. Listed
+// explicitly rather than matched by prefix: ui/title-scene-lighting-tokens.js
+// is still a production theme dependency.
+const REMOVED_RAY_TRACING_RUNTIME_MODULES = [
+ 'ui/title-screen.js',
+ 'ui/title-screen-optics.js',
+ 'ui/title-screen-presentation.js',
+ 'ui/title-screen-sample.js',
+ 'ui/title-scene.js',
+ 'ui/title-scene-camera.js',
+ 'ui/title-scene-cube.js',
+ 'ui/title-scene-director.js',
+ 'ui/title-scene-environment.js',
+ 'ui/title-scene-hit.js',
+ 'ui/title-scene-math.js',
+ 'ui/title-scene-mesh-hit.js',
+ 'ui/title-scene-ray-acceleration.js',
+ 'ui/title-scene-transform.js',
+ 'ui/title-mesh.js',
+ 'ui/title-mesh-library.js',
+ 'ui/title-mesh-ray-hit.js',
+ 'ui/title-bunny-scene.js',
+ 'ui/title-floor-light-effects.js',
+ 'app/title-camera-session.js',
+ 'app/title-camera-fps.js',
+ 'app/title-mesh-loader.js',
+ 'adapters/title-scene-loader.js',
+ 'adapters/title-scene-json-decoder.js',
+ 'adapters/title-scene-object-decoder.js',
+ 'adapters/title-scene-environment-decoder.js',
+ 'adapters/title-bunny-mesh.js',
+ 'adapters/workspace-title-meshes.js',
+];
+
+async function setup() {
+ const module = await importDist('app', 'workspace', 'runtime.js');
+ const deps = mockRuntime();
+ const { BijouI18nAdapter } = await importDist('adapters', 'bijou-i18n-adapter.js');
+ deps.initialModel.i18n = new BijouI18nAdapter();
+ deps.initialModel.jeditTheme = mockJeditTheme();
+ const runtime = module.createWorkspaceRuntime(deps);
+ const model = { ...runtime.init()[0], startupIntroComplete: true };
+ return { runtime, model };
+}
+
+function cells(surface) {
+ return Array.from({ length: surface.height }, (_, y) => (
+ Array.from({ length: surface.width }, (_, x) => surface.get(x, y))
+ ));
+}
+
+test('former title camera, shader, material, and scene-picker keys leave the static logo unchanged', async () => {
+ const { runtime, model } = await setup();
+ const baseline = cells(runtime.view(model));
+ for (const [key, modifiers] of [
+ ['space', {}], [' ', {}], ['w', {}], ['a', {}], ['s', {}], ['d', {}],
+ ['up', {}], ['down', {}], ['left', {}], ['right', {}], ['shift', {}],
+ ['1', {}], ['2', {}], ['.', {}], ['m', {}], ['M', {}], ['l', { ctrl: true }],
+ ]) {
+ const [next, commands] = runtime.update({ type: 'key', key, ...modifiers }, model);
+ assert.deepEqual(cells(runtime.view(next)), baseline, key);
+ assert.deepEqual(commands, [], key);
+ assert.equal(next, model, `${key} should not change idle title state`);
+ }
+});
+
+test('pointer movement does not activate a camera or change the title logo', async () => {
+ const { runtime, model } = await setup();
+ const baseline = cells(runtime.view(model));
+ let next = model;
+ for (const [col, row] of [[20, 10], [24, 12], [70, 15]]) {
+ const [updated, commands] = runtime.update({ type: 'mouse', action: 'move', button: 'none', col, row }, next);
+ assert.equal(updated, next);
+ assert.deepEqual(commands, []);
+ assert.deepEqual(cells(runtime.view(updated)), baseline);
+ next = updated;
+ }
+});
+
+test('stale scene and camera messages cannot change the workspace or reactivate animation', async () => {
+ const { runtime, model } = await setup();
+ for (const message of [
+ { type: 'load-scene-result', scene: { camera: { position: [0, 0, 1], target: [0, 0, 0], up: [0, 1, 0], fov: 60 } } },
+ { type: 'title-camera-frame', motionId: 1, value: 1 },
+ ]) {
+ const [next, commands] = runtime.update(message, model);
+ assert.equal(next, model);
+ assert.deepEqual(commands, []);
+ }
+ const [idle] = runtime.update({ type: 'time-tick', time: 30 }, model);
+ assert.equal(idle, model);
+});
+
+test('a freshly started static title is idle: time ticks return the same model', async () => {
+ const module = await importDist('app', 'workspace', 'runtime.js');
+ const runtime = module.createWorkspaceRuntime(mockRuntime());
+ const [initial] = runtime.init();
+ const [ticked, commands] = runtime.update({ type: 'time-tick', time: 1 }, initial);
+ assert.equal(ticked, initial);
+ assert.deepEqual(commands, []);
+});
+
+test('legacy scene properties cannot select a ray-traced viewer', async () => {
+ const { model } = await setup();
+ const { renderViewer } = await importDist('app', 'workspace', 'viewer-content.js');
+ const baseline = cells(renderViewer(model, 100, 28));
+ const stale = { ...model, titleBackdropKind: 'legacy-scene', sceneOverride: undefined, titleRenderMode: 'ascii' };
+ assert.deepEqual(cells(renderViewer(stale, 100, 28)), baseline);
+});
+
+test('the static title retains Settings, Files, and quit confirmation without advertising scene selection', async () => {
+ const { runtime, model } = await setup();
+ assert.equal(runtime.update({ type: 'key', key: 'f2' }, model)[0].settingsOpen, true);
+ assert.equal(runtime.update({ type: 'key', key: 'b', ctrl: true }, model)[0].fileDrawerOpen, true);
+ assert.equal(runtime.update({ type: 'key', key: 'escape' }, model)[0].quitConfirmOpen, true);
+ const rendered = cells(runtime.view(model)).map((row) => row.map((cell) => cell.char).join('')).join('\n');
+ assert.doesNotMatch(rendered, /scene picker|ctrl\+l/i);
+});
+
+test('the production workspace module graph does not load the ray tracer or scene loader', async (t) => {
+ await importDist('adapters', 'workspace-app.js');
+ const directory = mkdtempSync(path.join(tmpdir(), 'jim-static-modules-'));
+ t.after(() => rmSync(directory, { recursive: true, force: true }));
+ const log = path.join(directory, 'loaded.txt');
+ const loader = path.join(directory, 'loader.mjs');
+ writeFileSync(loader, `import { appendFileSync } from 'node:fs';
+export async function load(url, context, nextLoad) {
+ appendFileSync(${JSON.stringify(log)}, url + '\\n');
+ return nextLoad(url, context);
+}\n`);
+ const entry = pathToFileURL(path.join(REPO_ROOT, 'dist', 'adapters', 'workspace-app.js')).href;
+ const result = spawnSync(process.execPath, ['--no-warnings', '--experimental-loader', loader,
+ '--input-type=module', '-e', `await import(${JSON.stringify(entry)});`], { encoding: 'utf8' });
+ assert.equal(result.status, 0, result.stderr);
+ const loaded = readFileSync(log, 'utf8');
+ const distUrl = pathToFileURL(path.join(REPO_ROOT, 'dist')).href;
+ // Positive control: the forbidden checks below would pass vacuously on a URL prefix mismatch.
+ assert.equal(loaded.includes(`${distUrl}/adapters/workspace-app.js`), true, 'entry module URL');
+ for (const forbidden of REMOVED_RAY_TRACING_RUNTIME_MODULES) {
+ assert.equal(loaded.includes(`${distUrl}/${forbidden}`), false, forbidden);
+ }
+});
diff --git a/spec/title-braille-sampling-policy.spec.mjs b/spec/title-braille-sampling-policy.spec.mjs
deleted file mode 100644
index db6837cd..00000000
--- a/spec/title-braille-sampling-policy.spec.mjs
+++ /dev/null
@@ -1,242 +0,0 @@
-import assert from "node:assert/strict";
-import test from "node:test";
-import { importDist } from "./dist-helpers.mjs";
-
-const FAST_FRAME_MS = 12;
-const MODERATE_FRAME_MS = 48;
-const SLOW_FRAME_MS = 92;
-
-test("title Braille sampling keeps full quality without previous pressure facts", async () => {
- const sampling = await importDist(
- "app",
- "workspace",
- "title-braille-sampling.js",
- );
-
- assert.deepEqual(
- sampling.titleBrailleTraceBudget({
- frameIndex: 7,
- frameTimeMs: SLOW_FRAME_MS,
- }),
- {
- phase: 0,
- phaseCount: 1,
- },
- );
-});
-
-test("title Braille sampling ramps down when slow frames have high screen activity", async () => {
- const sampling = await importDist(
- "app",
- "workspace",
- "title-braille-sampling.js",
- );
-
- assert.deepEqual(
- sampling.titleBrailleTraceBudget({
- frameIndex: 9,
- frameTimeMs: SLOW_FRAME_MS,
- previousStats: activeStats(8, 8),
- }),
- {
- phase: 1,
- phaseCount: 4,
- },
- );
-});
-
-test("title Braille sampling treats close-mesh activity as pressure", async () => {
- const sampling = await importDist(
- "app",
- "workspace",
- "title-braille-sampling.js",
- );
-
- assert.deepEqual(
- sampling.titleBrailleTraceBudget({
- frameIndex: 6,
- frameTimeMs: SLOW_FRAME_MS,
- previousStats: activeStats(2, 8),
- }),
- {
- phase: 2,
- phaseCount: 4,
- },
- );
-});
-
-test("title Braille sampling only halves work for moderate pressure", async () => {
- const sampling = await importDist(
- "app",
- "workspace",
- "title-braille-sampling.js",
- );
-
- assert.deepEqual(
- sampling.titleBrailleTraceBudget({
- frameIndex: 3,
- frameTimeMs: MODERATE_FRAME_MS,
- previousStats: activeStats(8, 8),
- }),
- {
- phase: 1,
- phaseCount: 2,
- },
- );
-});
-
-test("title Braille sampling stays full quality when screen activity is low", async () => {
- const sampling = await importDist(
- "app",
- "workspace",
- "title-braille-sampling.js",
- );
-
- assert.deepEqual(
- sampling.titleBrailleTraceBudget({
- frameIndex: 4,
- frameTimeMs: SLOW_FRAME_MS,
- previousStats: activeStats(1, 8),
- }),
- {
- phase: 0,
- phaseCount: 1,
- },
- );
-});
-
-test("title Braille sampling uses ray pressure even when glyph activity is low", async () => {
- const sampling = await importDist(
- "app",
- "workspace",
- "title-braille-sampling.js",
- );
-
- assert.deepEqual(
- sampling.titleBrailleTraceBudget({
- frameIndex: 8,
- frameTimeMs: SLOW_FRAME_MS,
- previousStats: rayStats({
- activeSamples: 0,
- rayCount: 8,
- rayIntersectionCount: 3,
- }),
- }),
- {
- phase: 0,
- phaseCount: 4,
- },
- );
-});
-
-test("title Braille sampling ignores glyph activity when measured ray pressure is low", async () => {
- const sampling = await importDist(
- "app",
- "workspace",
- "title-braille-sampling.js",
- );
-
- assert.deepEqual(
- sampling.titleBrailleTraceBudget({
- frameIndex: 8,
- frameTimeMs: SLOW_FRAME_MS,
- previousStats: rayStats({
- activeSamples: 8,
- rayCount: 8,
- rayIntersectionCount: 0,
- }),
- }),
- {
- phase: 0,
- phaseCount: 1,
- },
- );
-});
-
-test("title Braille sampling holds reduced quality while screen activity remains high", async () => {
- const sampling = await importDist(
- "app",
- "workspace",
- "title-braille-sampling.js",
- );
-
- assert.deepEqual(
- sampling.titleBrailleTraceBudget({
- frameIndex: 5,
- frameTimeMs: FAST_FRAME_MS,
- previousStats: activeStats(8, 8),
- previousPhaseCount: 4,
- }),
- {
- phase: 1,
- phaseCount: 4,
- },
- );
-});
-
-test("title Braille sampling keeps full quality while the camera moves", async () => {
- const sampling = await importDist(
- "app",
- "workspace",
- "title-braille-sampling.js",
- );
-
- assert.deepEqual(
- sampling.titleBrailleTraceBudget({
- frameIndex: 10,
- frameTimeMs: FAST_FRAME_MS,
- previousStats: activeStats(8, 8),
- previousPhaseCount: 4,
- cameraMoving: true,
- }),
- {
- phase: 0,
- phaseCount: 1,
- },
- );
-});
-
-test("title Braille sampling restores full quality when screen activity drops", async () => {
- const sampling = await importDist(
- "app",
- "workspace",
- "title-braille-sampling.js",
- );
-
- assert.deepEqual(
- sampling.titleBrailleTraceBudget({
- frameIndex: 5,
- frameTimeMs: FAST_FRAME_MS,
- previousStats: activeStats(1, 8),
- previousPhaseCount: 4,
- }),
- {
- phase: 0,
- phaseCount: 1,
- },
- );
-});
-
-function activeStats(activeSamples, totalSamples) {
- return {
- totalSamples,
- tracedSamples: totalSamples,
- reusedSamples: 0,
- activeSamples,
- coldMissSamples: 0,
- rayCount: 0,
- rayIntersectionCount: 0,
- };
-}
-
-function rayStats(options) {
- return {
- totalSamples: 8,
- tracedSamples: 8,
- reusedSamples: 0,
- activeSamples: options.activeSamples,
- coldMissSamples: 0,
- rayCount: options.rayCount,
- rayIntersectionCount: options.rayIntersectionCount,
- };
-}
diff --git a/spec/title-material-key.spec.mjs b/spec/title-material-key.spec.mjs
deleted file mode 100644
index 7b904401..00000000
--- a/spec/title-material-key.spec.mjs
+++ /dev/null
@@ -1,59 +0,0 @@
-import assert from "node:assert/strict";
-import test from "node:test";
-import { importDist } from "./dist-helpers.mjs";
-import { mockKeyBindingContext, mockTitleScreenModel } from "./workspace-helpers.mjs";
-
-// Pressing `m` with no scene loaded used to switch the ray-traced backdrop on
-// and toast that a material preset had been applied. Nothing was applied: the
-// generated backdrop derives its materials from the theme and never reads
-// titleMeshMaterialIndex. It reported a change that could not have happened,
-// and switched on an animation loop to do it.
-
-const MATERIAL_KEY = { key: "m", ctrl: false, alt: false, shift: false };
-
-test("the material key does not claim success with no scene loaded", async () => {
- const [keys, titleScreen] = await Promise.all([
- importDist("app", "workspace", "title-screen-key-bindings.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const model = mockTitleScreenModel(titleScreen, { sceneOverride: undefined });
-
- const [next] = keys.updateTitleScreenKey(MATERIAL_KEY, model, mockKeyBindingContext());
-
- assert.equal(
- next.titleBackdropKind,
- model.titleBackdropKind,
- "the material key must not switch the legacy backdrop on",
- );
- const toast = next.notifications?.items?.at(-1);
- assert.ok(toast != null, "expected the reader to be told why nothing happened");
- assert.match(
- `${toast.title} ${toast.message}`,
- /scene/i,
- `expected a message about loading a scene, got "${toast.message}"`,
- );
-});
-
-test("the material key still cycles presets when a scene is loaded", async () => {
- const [keys, titleScreen] = await Promise.all([
- importDist("app", "workspace", "title-screen-key-bindings.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const scene = {
- camera: { position: [0, 0, 1], target: [0, 0, 0], up: [0, 1, 0], fov: 60 },
- objects: [],
- environment: {},
- };
- const model = mockTitleScreenModel(titleScreen, {
- sceneOverride: scene,
- titleMeshMaterialIndex: 0,
- });
-
- const [next] = keys.updateTitleScreenKey(MATERIAL_KEY, model, mockKeyBindingContext());
-
- assert.notEqual(
- next.titleMeshMaterialIndex,
- model.titleMeshMaterialIndex,
- "a loaded scene should still cycle the preset",
- );
-});
diff --git a/spec/title-scene-load-failure.spec.mjs b/spec/title-scene-load-failure.spec.mjs
deleted file mode 100644
index 5712c6f6..00000000
--- a/spec/title-scene-load-failure.spec.mjs
+++ /dev/null
@@ -1,68 +0,0 @@
-import assert from "node:assert/strict";
-import test from "node:test";
-import { importDist, discoverRepoRoot } from "./dist-helpers.mjs";
-import { mockI18n, mockJeditTheme } from "./workspace-helpers.mjs";
-
-// A scene load that returns no scene -- cancelled, or a RuntimeIssue -- must
-// leave the backdrop as it found it. Switching to LegacyScene with no scene to
-// draw does two things: it drops the static logo, and because
-// workspaceAnimationIsActive treats LegacyScene as animating, it puts the
-// workspace back into a 60Hz render loop. That is the idle-render regression
-// from #320, resurrected on the failure path.
-
-async function initialModel() {
- const init = await importDist("app", "workspace", "init.js");
- return init.createInitialModel(discoverRepoRoot(), 120, 40, {
- entries: [],
- jeditTheme: mockJeditTheme(),
- i18n: mockI18n(),
- nowMs: 0,
- });
-}
-
-test("a cancelled scene load leaves the backdrop alone", async () => {
- const [state, screen, init] = await Promise.all([
- importDist("app", "workspace", "workspace-title-scene-state.js"),
- importDist("ui", "title-screen.js"),
- importDist("app", "workspace", "init.js"),
- ]);
- const model = { ...(await initialModel()), startupIntroComplete: true };
-
- assert.equal(
- model.titleBackdropKind,
- screen.TITLE_BACKDROP_KIND.StaticLogo,
- "precondition: the workspace starts on the static logo",
- );
-
- const next = state.applyWorkspaceTitleSceneLoadResult(model, {
- type: "loadSceneResult",
- scene: null,
- sceneName: "continuum-gate",
- });
-
- assert.equal(next.titleBackdropKind, screen.TITLE_BACKDROP_KIND.StaticLogo);
- assert.equal(
- init.workspaceAnimationIsActive(next),
- false,
- "a failed load must not restart the render loop",
- );
-});
-
-test("a successful scene load does switch to the legacy backdrop", async () => {
- const [state, screen] = await Promise.all([
- importDist("app", "workspace", "workspace-title-scene-state.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const model = await initialModel();
-
- const scene = { camera: { position: [0, 0, 1], target: [0, 0, 0], up: [0, 1, 0], fov: 60 } };
- const next = state.applyWorkspaceTitleSceneLoadResult(model, {
- type: "loadSceneResult",
- scene,
- sceneName: "continuum-gate",
- });
-
- assert.equal(next.titleBackdropKind, screen.TITLE_BACKDROP_KIND.LegacyScene);
- assert.equal(next.titleSceneName, "continuum-gate");
- assert.notEqual(next.sceneOverride, null);
-});
diff --git a/spec/workspace-command-line.spec.mjs b/spec/workspace-command-line.spec.mjs
index 5f480f6e..5f31339b 100644
--- a/spec/workspace-command-line.spec.mjs
+++ b/spec/workspace-command-line.spec.mjs
@@ -1848,11 +1848,6 @@ test("colon does not enter command mode while higher-priority overlays own focus
focusPane: "editor",
settingsOpen: true,
}),
- mockTitleScreenModel(titleScreen, {
- editor: mockEditor(editorMode),
- focusPane: "editor",
- scenePickerOpen: true,
- }),
mockTitleScreenModel(titleScreen, {
editor: undefined,
focusPane: "editor",
diff --git a/spec/workspace-fast-startup.spec.mjs b/spec/workspace-fast-startup.spec.mjs
index 5f0960b1..7f5099c9 100644
--- a/spec/workspace-fast-startup.spec.mjs
+++ b/spec/workspace-fast-startup.spec.mjs
@@ -94,45 +94,6 @@ test("the startup logo carries its own colours, not one flat token", async () =>
assert.ok(inkColours.size > 3, `expected multiple ink colours, saw ${inkColours.size}`);
});
-test("no title scene stats are reported when no backdrop is drawn", async () => {
- const [init, stats] = await Promise.all([
- importDist("app", "workspace", "init.js"),
- importDist("app", "workspace", "title-scene-performance-stats.js"),
- ]);
- const model = init.createInitialModel(REPO_ROOT, VIEWER_WIDTH, VIEWER_HEIGHT, {
- entries: [],
- titleSceneSeed: FIXED_SEED,
- jeditTheme: mockJeditTheme(),
- i18n: mockI18n(),
- nowMs: STARTUP_NOW_MS,
- });
-
- assert.equal(stats.titleScenePerformanceStats(model), undefined);
-});
-
-test("title scene stats are reported once the legacy backdrop is selected", async () => {
- const [init, stats, titleScreen] = await Promise.all([
- importDist("app", "workspace", "init.js"),
- importDist("app", "workspace", "title-scene-performance-stats.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const model = init.createInitialModel(REPO_ROOT, VIEWER_WIDTH, VIEWER_HEIGHT, {
- entries: [],
- titleSceneSeed: FIXED_SEED,
- jeditTheme: mockJeditTheme(),
- i18n: mockI18n(),
- nowMs: STARTUP_NOW_MS,
- });
-
- const reported = stats.titleScenePerformanceStats({
- ...model,
- titleBackdropKind: titleScreen.TITLE_BACKDROP_KIND.LegacyScene,
- });
-
- assert.notEqual(reported, undefined);
- assert.ok(reported.rayCount > 0);
-});
-
test("an idle time tick returns the same model so nothing re-renders", async () => {
const [runtimeModule, titleScreen] = await Promise.all([
importDist("app", "workspace", "runtime.js"),
@@ -167,7 +128,7 @@ test("a time tick still animates while the perf overlay is visible", async () =>
assert.equal(next.time, 1);
});
-test("a time tick still animates while the legacy title backdrop is drawn", async () => {
+test("a stale legacy backdrop flag does not reactivate animation", async () => {
const [runtimeModule, titleScreen] = await Promise.all([
importDist("app", "workspace", "runtime.js"),
importDist("ui", "title-screen.js"),
@@ -180,7 +141,7 @@ test("a time tick still animates while the legacy title backdrop is drawn", asyn
const [next] = runtime.update({ type: "time-tick", time: 1 }, tracing);
- assert.notEqual(next, tracing);
+ assert.equal(next, tracing);
});
function idleWorkspaceModel(titleScreen) {
diff --git a/spec/workspace-footer.spec.mjs b/spec/workspace-footer.spec.mjs
index e93d1a06..c744ce15 100644
--- a/spec/workspace-footer.spec.mjs
+++ b/spec/workspace-footer.spec.mjs
@@ -288,7 +288,7 @@ test('workspace footer shows settings drawer controls while settings are open',
);
});
-test('workspace footer obtains the scene picker hint label from i18n', async () => {
+test('workspace footer requests available file and graft hints without scene selection', async () => {
const footer = await loadFooterModule();
const requestedKeys = [];
const i18n = {
@@ -335,11 +335,11 @@ test('workspace footer obtains the scene picker hint label from i18n', async ()
graftSelection: undefined,
}),
[
- 'BROWSE [ctrl+l scene picker · ctrl+b files · ctrl+g graft]',
+ 'BROWSE [ctrl+b files · ctrl+g graft]',
'/repo',
],
);
- assert.equal(requestedKeys.includes('footer.hints.ctrl_l_scene_picker'), true);
+ assert.equal(requestedKeys.includes('footer.hints.ctrl_l_scene_picker'), false);
});
test('workspace footer obtains command hints from i18n', async () => {
diff --git a/spec/workspace-key-bindings.spec.mjs b/spec/workspace-key-bindings.spec.mjs
index 5b7602f4..b813f449 100644
--- a/spec/workspace-key-bindings.spec.mjs
+++ b/spec/workspace-key-bindings.spec.mjs
@@ -40,11 +40,6 @@ test("backtick key remains a perf toggle while workspace overlays are open", asy
markdownPreviewActive: false,
viewMode: "source",
}),
- mockTitleScreenModel(titleScreen, {
- scenePickerOpen: true,
- scenePickerFocusIndex: 0,
- availableScenes: ["bunny.jedit-scene"],
- }),
mockTitleScreenModel(titleScreen, {
startupIntroComplete: true,
startupFileModalOpen: true,
@@ -86,24 +81,6 @@ test("ctrl-c still quits while startup file modal is open", async () => {
assert.equal(await commands[0](), QUIT);
});
-test("ctrl-l opens the title scene picker when no editor is active", async () => {
- const [keyBindings, titleScreen] = await Promise.all([
- importDist("app", "workspace", "key-bindings.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const [nextModel] = keyBindings.updateFromKey(
- { type: "key", key: "l", ctrl: true, alt: false, shift: false },
- mockTitleScreenModel(titleScreen, { scenePickerOpen: false }),
- mockKeyBindingContext(),
- );
-
- assert.equal(nextModel.scenePickerOpen, true);
- assert.equal(
- nextModel.titleBackdropKind,
- titleScreen.TITLE_BACKDROP_KIND.LegacyScene,
- );
-});
-
test("ctrl-h cannot open a process-local Echo history pane", async () => {
const [keyBindings, titleScreen, editorMode] = await Promise.all([
importDist("app", "workspace", "key-bindings.js"),
@@ -176,141 +153,3 @@ test("Bijou quit confirmation accepts y and dismisses q without editor input", a
assert.equal(dismissed.quitConfirmOpen, false);
assert.deepEqual(dismissCommands, []);
});
-
-test("scene picker loads built-in scenes by name without using workspace root paths", async () => {
- const [keyBindings, titleScreen] = await Promise.all([
- importDist("app", "workspace", "key-bindings.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const scene = { camera: { angle: 0, radius: 8.5 }, objects: [] };
- const requestedScenes = [];
- const deps = {
- ...mockDeps(),
- fileSystem: {
- ...mockDeps().fileSystem,
- join: () => {
- throw new Error(
- "workspaceRoot path should not be used for built-in scenes",
- );
- },
- },
- titleSceneLoader: {
- loadTitleSceneFromFile: async () => {
- throw new Error(
- "file scene loader should not be used for built-in scenes",
- );
- },
- loadBuiltInTitleScene: async (name) => {
- requestedScenes.push(name);
- return scene;
- },
- },
- };
- const [, commands] = keyBindings.updateFromKey(
- { type: "key", key: "enter", ctrl: false, alt: false, shift: false },
- mockTitleScreenModel(titleScreen, {
- workspaceRoot: "/tmp/not-jedit-rays",
- scenePickerOpen: true,
- scenePickerFocusIndex: 0,
- availableScenes: ["bunny.jedit-scene"],
- titleMeshes: {},
- }),
- mockKeyBindingContext({ deps }),
- );
- const message = await commands[0]();
-
- assert.deepEqual(requestedScenes, ["bunny.jedit-scene"]);
- assert.deepEqual(message, {
- type: "load-scene-result",
- scene,
- sceneName: "bunny.jedit-scene",
- });
-});
-
-test("scene picker load failures preserve structured runtime issue detail", async () => {
- const [keyBindings, titleScreen] = await Promise.all([
- importDist("app", "workspace", "key-bindings.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const failure = new Error("scene missing");
- const deps = {
- ...mockDeps(),
- titleSceneLoader: {
- loadTitleSceneFromFile: async () => undefined,
- loadBuiltInTitleScene: async () => {
- throw failure;
- },
- },
- };
- const [, commands] = keyBindings.updateFromKey(
- { type: "key", key: "enter", ctrl: false, alt: false, shift: false },
- mockTitleScreenModel(titleScreen, {
- scenePickerOpen: true,
- scenePickerFocusIndex: 0,
- availableScenes: ["missing.jedit-scene"],
- titleMeshes: {},
- }),
- mockKeyBindingContext({ nowMs: () => 987, deps }),
- );
-
- const message = await commands[0]();
-
- assert.equal(message.type, "runtime-issue");
- assert.equal(message.issue.message, "scene missing");
- assert.equal(message.issue.stack, failure.stack);
- assert.equal(message.issue.level, "error");
- assert.equal(message.issue.source, "command");
- assert.equal(message.issue.atMs, 987);
-});
-
-test("scene picker load failures preserve diagnostics for circular thrown values", async () => {
- const [keyBindings, titleScreen] = await Promise.all([
- importDist("app", "workspace", "key-bindings.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const circular = {};
- circular.self = circular;
- const deps = {
- ...mockDeps(),
- titleSceneLoader: {
- loadTitleSceneFromFile: async () => undefined,
- loadBuiltInTitleScene: async () => {
- throw circular;
- },
- },
- };
- const [, commands] = keyBindings.updateFromKey(
- { type: "key", key: "enter", ctrl: false, alt: false, shift: false },
- mockTitleScreenModel(titleScreen, {
- scenePickerOpen: true,
- scenePickerFocusIndex: 0,
- availableScenes: ["circular.jedit-scene"],
- titleMeshes: {},
- }),
- mockKeyBindingContext({ nowMs: () => 654, deps }),
- );
-
- const message = await commands[0]();
-
- assert.equal(message.type, "runtime-issue");
- assert.equal(message.issue.name, "SceneLoadError");
- assert.equal(message.issue.atMs, 654);
-});
-
-test("scene picker keeps focus index non-negative when no scenes are available", async () => {
- const [keyBindings, titleScreen] = await Promise.all([
- importDist("app", "workspace", "key-bindings.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const [nextModel] = keyBindings.updateFromKey(
- { key: "down" },
- mockTitleScreenModel(titleScreen, {
- scenePickerOpen: true,
- scenePickerFocusIndex: 0,
- availableScenes: [],
- }),
- mockKeyBindingContext(),
- );
-
- assert.equal(nextModel.scenePickerFocusIndex, 0);
-});
diff --git a/spec/workspace-runtime.spec.mjs b/spec/workspace-runtime.spec.mjs
index 61ee33af..645297c0 100644
--- a/spec/workspace-runtime.spec.mjs
+++ b/spec/workspace-runtime.spec.mjs
@@ -96,47 +96,6 @@ test("raytracer profiler uses centralized runtime issue tokens", async () => {
assert.doesNotMatch(source, /const ISSUE_SOURCE_COMMAND/);
});
-test("runtime load-scene-result applies the loaded scene camera to title camera state", async () => {
- const runtimeModule = await importDist("app", "workspace", "runtime.js");
- const runtime = runtimeModule.createWorkspaceRuntime(mockRuntime());
- const scene = {
- camera: {
- angle: 1.25,
- radius: 6.75,
- position: [0.8, 1.05, 2.5],
- target: [0, 0.8, 0],
- },
- objects: [],
- };
-
- const [nextModel] = runtime.update(
- { type: "load-scene-result", scene },
- {
- sceneOverride: undefined,
- titleCamera: {
- angle: 9,
- angleTarget: 9,
- angleMotionId: 3,
- radius: 9,
- radiusTarget: 9,
- radiusMotionId: 4,
- position: [0, 2.65, 9],
- target: [0, 0.78, 0],
- eyeY: 2.65,
- },
- },
- );
-
- assert.equal(nextModel.sceneOverride, scene);
- assert.equal(nextModel.titleCamera.angle, scene.camera.angle);
- assert.equal(nextModel.titleCamera.angleTarget, scene.camera.angle);
- assert.equal(nextModel.titleCamera.radius, scene.camera.radius);
- assert.equal(nextModel.titleCamera.radiusTarget, scene.camera.radius);
- assert.deepEqual(nextModel.titleCamera.position, scene.camera.position);
- assert.deepEqual(nextModel.titleCamera.target, scene.camera.target);
- assert.equal(nextModel.titleCamera.eyeY, scene.camera.position[1]);
-});
-
test("perf app renders overlay after toggle when perf starts disabled", async () => {
const workspacePerfApp = await importDist("adapters", "workspace-perf-app.js");
const app = workspacePerfApp.createPerfApp(
@@ -211,29 +170,6 @@ test("workspace runtime starts the profile session on init when requested", asyn
assert.equal(profiledModel.profiler.filePath, "/repo/.jedit/perf-session.jsonl");
});
-test("workspace perf overlay adds title-scene facts only on title screen", async () => {
- const [workspacePerfApp, titleScreen] = await Promise.all([
- importDist("adapters", "workspace-perf-app.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const model = {
- ...mockPerfTitleModel(titleScreen),
- titleBackdropKind: titleScreen.TITLE_BACKDROP_KIND.LegacyScene,
- };
- const app = workspacePerfApp.createPerfApp(surfaceOnlyApp(), {
- initialPerfVisible: true,
- });
- const titleText = surfaceText(app.view(model));
- const editorText = surfaceText(app.view({ ...model, editor: { path: "x" } }));
-
- assert.match(titleText, /title scene/);
- assert.match(titleText, /scene\s+default-scene/);
- assert.match(titleText, /objects\s+1/);
- assert.match(titleText, /triangles\s+3/);
- assert.match(titleText, /rays\s+5120/);
- assert.doesNotMatch(editorText, /title scene/);
-});
-
test("initial workspace model rejects stale non-Echo seeded text runtime profile", async () => {
const [initModule, themes, profile] = await Promise.all([
importDist("app", "workspace", "init.js"),
@@ -293,41 +229,6 @@ test("initial workspace model separates text authority from render cache", async
assert.equal("requestStop" in model.textAuthority, false);
});
-test("initial workspace scene picker lists authored scene assets that exist on disk", async () => {
- const initModule = await importDist("app", "workspace", "init.js");
- const model = initModule.createInitialModel("/repo", 120, 24, {
- titleSceneSeed: 0.5,
- jeditTheme: mockJeditTheme(),
- i18n: mockI18n(),
- entries: [],
- nowMs: 0,
- });
-
- assert.deepEqual(model.availableScenes, [
- "continuum-gate.jedit-scene",
- "bunny.jedit-scene",
- "neon-dispersion.jedit-scene",
- "teapot-cornell.jedit-scene",
- "teapot-gallery.jedit-scene",
- "neon-orbit.jedit-scene",
- "mirror-hall.jedit-scene",
- "eclipse-gate.jedit-scene",
- "prism-garden.jedit-scene",
- "aurora-vault.jedit-scene",
- "ember-court.jedit-scene",
- "material-lab.jedit-scene",
- "sphere.jedit-scene",
- "column.jedit-scene",
- "sphere-ground.jedit-scene",
- ]);
- assert.equal(
- model.availableScenes.every((scene) =>
- existsSync(path.join(REPO_ROOT, "scenes", scene)),
- ),
- true,
- );
-});
-
test("runtime trims frame history to the configured window", async () => {
const runtimeModule = await importDist("app", "workspace", "runtime.js");
const runtime = runtimeModule.createWorkspaceRuntime({
@@ -339,7 +240,7 @@ test("runtime trims frame history to the configured window", async () => {
{ type: "time-tick", time: 2 },
{
...initialModel,
- lastFrameMs: 100,
+ lastFrameMs: 100, perfVisible: true,
frameTimeHistory: Array.from({ length: 55 }, (_, index) => index),
},
);
diff --git a/spec/workspace-title-braille-sampling.spec.mjs b/spec/workspace-title-braille-sampling.spec.mjs
deleted file mode 100644
index d242e275..00000000
--- a/spec/workspace-title-braille-sampling.spec.mjs
+++ /dev/null
@@ -1,285 +0,0 @@
-import assert from "node:assert/strict";
-import test from "node:test";
-import { stringToSurface } from "@flyingrobots/bijou";
-import {
- importDist,
- mockTitleScreenModel,
- surfaceText,
-} from "./workspace-helpers.mjs";
-
-const TITLE_WIDTH = 44;
-const TITLE_HEIGHT = 6;
-const FAST_FRAME_MS = 12;
-const SLOW_FRAME_MS = 92;
-
-test("viewer renderer passes adaptive Braille sampling state to live title frames", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const budgets = [];
- const cacheObjects = [];
- const renderer = viewerContent.createViewerContentRenderer(
- (width, height, _time, _theme, options) => {
- budgets.push(options.brailleSampling?.traceBudget);
- cacheObjects.push(options.brailleSampling?.sampleCache);
- markHighActivity(options.brailleSampling?.stats);
- return stringToSurface(`frame ${budgets.length}`, width, height);
- },
- );
- const base = mockTitleScreenModel(titleScreen, {
- frameTimeMs: FAST_FRAME_MS,
- startupIntroComplete: true,
- startupFileModalOpen: true,
- });
-
- const first = renderer.renderViewer(base, TITLE_WIDTH, TITLE_HEIGHT);
- const second = renderer.renderViewer(
- {
- ...base,
- frameTimeMs: SLOW_FRAME_MS,
- time: 1,
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
- const third = renderer.renderViewer(
- {
- ...base,
- frameTimeMs: FAST_FRAME_MS,
- time: 2,
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
-
- assert.equal(surfaceText(first).includes("frame 1"), true);
- assert.equal(surfaceText(second).includes("frame 2"), true);
- assert.equal(surfaceText(third).includes("frame 3"), true);
- assert.deepEqual(budgets, [
- {
- phase: 0,
- phaseCount: 1,
- },
- {
- phase: 1,
- phaseCount: 4,
- },
- {
- phase: 2,
- phaseCount: 4,
- },
- ]);
- assert.equal(cacheObjects[0], cacheObjects[1]);
- assert.equal(cacheObjects[1], cacheObjects[2]);
-});
-
-test("viewer renderer does not allocate Braille sampling for ASCII title frames", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const samplingOptions = [];
- const renderer = viewerContent.createViewerContentRenderer(
- (width, height, _time, _theme, options) => {
- samplingOptions.push(options.brailleSampling);
- return stringToSurface("ascii frame", width, height);
- },
- );
-
- renderer.renderViewer(
- mockTitleScreenModel(titleScreen, {
- titleRenderMode: titleScreen.TITLE_RENDER_MODE.Ascii,
- }),
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
-
- assert.deepEqual(samplingOptions, [undefined]);
-});
-
-test("viewer renderer resets adaptive Braille history after ASCII title frames", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const budgets = [];
- const renderer = viewerContent.createViewerContentRenderer(
- (width, height, _time, _theme, options) => {
- budgets.push(options.brailleSampling?.traceBudget);
- markHighActivity(options.brailleSampling?.stats);
- return stringToSurface("frame", width, height);
- },
- );
- const base = mockTitleScreenModel(titleScreen, {
- frameTimeMs: FAST_FRAME_MS,
- startupIntroComplete: true,
- startupFileModalOpen: true,
- });
-
- renderer.renderViewer(base, TITLE_WIDTH, TITLE_HEIGHT);
- renderer.renderViewer(
- {
- ...base,
- frameTimeMs: SLOW_FRAME_MS,
- time: 1,
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
- renderer.renderViewer(
- {
- ...base,
- titleRenderMode: titleScreen.TITLE_RENDER_MODE.Ascii,
- time: 2,
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
- renderer.renderViewer(
- {
- ...base,
- time: 3,
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
-
- assert.deepEqual(budgets, [
- { phase: 0, phaseCount: 1 },
- { phase: 1, phaseCount: 4 },
- undefined,
- { phase: 0, phaseCount: 1 },
- ]);
-});
-
-test("viewer renderer keeps full Braille sampling while the title camera settles", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const budgets = [];
- const renderer = viewerContent.createViewerContentRenderer(
- (width, height, _time, _theme, options) => {
- budgets.push(options.brailleSampling?.traceBudget);
- markHighActivity(options.brailleSampling?.stats);
- return stringToSurface("frame", width, height);
- },
- );
- const base = mockTitleScreenModel(titleScreen, {
- frameTimeMs: FAST_FRAME_MS,
- startupIntroComplete: false,
- startupFileModalOpen: false,
- });
-
- renderer.renderViewer(base, TITLE_WIDTH, TITLE_HEIGHT);
- renderer.renderViewer(
- {
- ...base,
- time: 1,
- titleCamera: {
- ...base.titleCamera,
- angleTarget: 0.2,
- },
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
-
- assert.deepEqual(budgets, [
- { phase: 0, phaseCount: 1 },
- { phase: 0, phaseCount: 1 },
- ]);
-});
-
-test("viewer renderer resets Braille samples after FPS camera movement", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const budgets = [];
- const cacheObjects = [];
- const renderer = viewerContent.createViewerContentRenderer(
- (width, height, _time, _theme, options) => {
- budgets.push(options.brailleSampling?.traceBudget);
- cacheObjects.push(options.brailleSampling?.sampleCache);
- markHighActivity(options.brailleSampling?.stats);
- return stringToSurface("frame", width, height);
- },
- );
- const base = mockTitleScreenModel(titleScreen, {
- frameTimeMs: FAST_FRAME_MS,
- startupIntroComplete: false,
- startupFileModalOpen: false,
- });
-
- renderer.renderViewer(base, TITLE_WIDTH, TITLE_HEIGHT);
- renderer.renderViewer(
- {
- ...base,
- time: 1,
- titleCamera: {
- ...base.titleCamera,
- position: [0, 2.65, 8],
- target: [0, 0.78, -0.5],
- },
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
-
- assert.deepEqual(budgets, [
- { phase: 0, phaseCount: 1 },
- { phase: 0, phaseCount: 1 },
- ]);
- assert.notEqual(cacheObjects[0], cacheObjects[1]);
-});
-
-test("viewer renderer resets Braille samples after title material changes", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const cacheObjects = [];
- const renderer = viewerContent.createViewerContentRenderer(
- (width, height, _time, _theme, options) => {
- cacheObjects.push(options.brailleSampling?.sampleCache);
- markHighActivity(options.brailleSampling?.stats);
- return stringToSurface("frame", width, height);
- },
- );
- const sceneOverride = {
- camera: { angle: 0, radius: 8 },
- objects: [],
- };
- const base = mockTitleScreenModel(titleScreen, {
- frameTimeMs: FAST_FRAME_MS,
- sceneOverride,
- startupIntroComplete: false,
- startupFileModalOpen: false,
- });
-
- renderer.renderViewer(base, TITLE_WIDTH, TITLE_HEIGHT);
- renderer.renderViewer(
- {
- ...base,
- sceneOverride: { ...sceneOverride },
- time: 1,
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
-
- assert.notEqual(cacheObjects[0], cacheObjects[1]);
-});
-
-function markHighActivity(stats) {
- if (stats == null) {
- return;
- }
- stats.totalSamples = 8;
- stats.tracedSamples = 8;
- stats.reusedSamples = 0;
- stats.activeSamples = 8;
- stats.coldMissSamples = 0;
-}
diff --git a/spec/workspace-title-performance-governor.spec.mjs b/spec/workspace-title-performance-governor.spec.mjs
deleted file mode 100644
index c9a9f6fb..00000000
--- a/spec/workspace-title-performance-governor.spec.mjs
+++ /dev/null
@@ -1,368 +0,0 @@
-import assert from "node:assert/strict";
-import test from "node:test";
-import { stringToSurface } from "@flyingrobots/bijou";
-import {
- importDist,
- mockTitleScreenModel,
- surfaceText,
-} from "./workspace-helpers.mjs";
-
-const TITLE_WIDTH = 44;
-const TITLE_HEIGHT = 6;
-const SLOW_FRAME_MS = 90;
-const FAST_FRAME_MS = 12;
-
-test("default Jim logo renderer reports static no-ray performance facts", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const renderer = viewerContent.createViewerContentRenderer();
- renderer.renderViewer(
- mockTitleScreenModel(titleScreen, { startupIntroComplete: true }),
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
-
- assert.deepEqual(renderer.titleScenePerformanceFacts(), {
- posture: "static-title",
- tracesRays: false,
- usesFrozenBackdrop: false,
- retainsBackdrop: false,
- inputLatencyPosture: "static-title",
- frameBudgetPosture: "within-budget",
- });
-});
-
-test("title scene performance governor selects low-rate frozen backdrop with ray tracing disabled", async () => {
- const governor = await importDist(
- "app",
- "workspace",
- "title-scene-performance-governor.js",
- );
-
- assert.deepEqual(
- governor.governTitleSceneRender({
- introActive: true,
- idleTitleScreen: false,
- frozenBackdropAvailable: true,
- cacheAgeSeconds: 0,
- lowRateFrozenBackdropActive: true,
- frameTimeMs: SLOW_FRAME_MS,
- }),
- {
- posture: governor.TITLE_SCENE_RENDER_POSTURE.LowRateFrozenBackdrop,
- shouldTraceRays: false,
- shouldUseFrozenBackdrop: true,
- shouldRetainRenderedBackdrop: false,
- inputLatencyPosture: "low-rate-title",
- frameBudgetPosture: "over-budget",
- },
- );
-
- assert.deepEqual(
- governor.governTitleSceneRender({
- introActive: false,
- idleTitleScreen: false,
- frozenBackdropAvailable: true,
- cacheAgeSeconds: 0.2,
- frameTimeMs: SLOW_FRAME_MS,
- }),
- {
- posture: governor.TITLE_SCENE_RENDER_POSTURE.LiveTrace,
- shouldTraceRays: true,
- shouldUseFrozenBackdrop: false,
- shouldRetainRenderedBackdrop: true,
- inputLatencyPosture: "animated-title",
- frameBudgetPosture: "over-budget",
- },
- );
-
- assert.deepEqual(
- governor.governTitleSceneRender({
- introActive: false,
- idleTitleScreen: false,
- frozenBackdropAvailable: false,
- frameTimeMs: FAST_FRAME_MS,
- }),
- {
- posture: governor.TITLE_SCENE_RENDER_POSTURE.LiveTrace,
- shouldTraceRays: true,
- shouldUseFrozenBackdrop: false,
- shouldRetainRenderedBackdrop: true,
- inputLatencyPosture: "animated-title",
- frameBudgetPosture: "within-budget",
- },
- );
-
- assert.deepEqual(
- governor.governTitleSceneRender({
- introActive: false,
- idleTitleScreen: true,
- frozenBackdropAvailable: true,
- cacheAgeSeconds: 0.1,
- lowRateFrozenBackdropActive: true,
- frameTimeMs: FAST_FRAME_MS,
- }).posture,
- governor.TITLE_SCENE_RENDER_POSTURE.LowRateFrozenBackdrop,
- );
-
- assert.deepEqual(
- governor.governTitleSceneRender({
- introActive: false,
- idleTitleScreen: true,
- frozenBackdropAvailable: true,
- cacheAgeSeconds: 0.1,
- frameTimeMs: SLOW_FRAME_MS,
- }).posture,
- governor.TITLE_SCENE_RENDER_POSTURE.LowRateFrozenBackdrop,
- );
-});
-
-test("viewer renderer composites startup intro logos without retracing backdrop", async () => {
- const [viewerContent, titleScreen, governor] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- importDist("app", "workspace", "title-scene-performance-governor.js"),
- ]);
- const tracedTimes = [];
- const renderedBackdrops = [];
- const renderer = viewerContent.createViewerContentRenderer(
- (width, height, time, theme, options) => {
- assert.equal(options.suppressPresentation, true);
- tracedTimes.push(time);
- const backdrop = stringToSurface(
- `backdrop trace ${tracedTimes.length} time ${time}`,
- width,
- height,
- );
- renderedBackdrops.push(backdrop);
- return backdrop;
- },
- );
- const base = mockTitleScreenModel(titleScreen, {
- time: 2,
- frameTimeMs: SLOW_FRAME_MS,
- startupIntroComplete: false,
- startupFileModalOpen: false,
- });
-
- const first = renderer.renderViewer(base, 96, 28);
- const backdropText = surfaceText(renderedBackdrops[0]);
- const second = renderer.renderViewer(
- {
- ...base,
- time: 2 + governor.TITLE_SCENE_LOW_RATE_REFRESH_SECONDS * 2,
- frameTimeMs: FAST_FRAME_MS,
- },
- 96,
- 28,
- );
-
- assert.deepEqual(tracedTimes, [2]);
- assert.notEqual(first, renderedBackdrops[0]);
- assert.notEqual(second, renderedBackdrops[0]);
- assert.equal(surfaceText(renderedBackdrops[0]), backdropText);
- assert.notEqual(surfaceText(second), backdropText);
- assert.deepEqual(renderer.titleScenePerformanceFacts(), {
- posture: "low-rate-frozen-backdrop",
- tracesRays: false,
- usesFrozenBackdrop: true,
- retainsBackdrop: false,
- inputLatencyPosture: "low-rate-title",
- frameBudgetPosture: "within-budget",
- });
-});
-
-test("viewer renderer reuses pure intro backdrop after intro without preserving presentation", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const tracedTimes = [];
- const renderedBackdrops = [];
- const renderer = viewerContent.createViewerContentRenderer(
- (width, height, time, theme, options) => {
- assert.equal(options.suppressPresentation, true);
- tracedTimes.push(time);
- const backdrop = stringToSurface(
- `pure backdrop trace ${tracedTimes.length}`,
- width,
- height,
- );
- renderedBackdrops.push(backdrop);
- return backdrop;
- },
- );
- const intro = mockTitleScreenModel(titleScreen, {
- time: 2,
- frameTimeMs: SLOW_FRAME_MS,
- startupIntroComplete: false,
- startupFileModalOpen: false,
- });
-
- const introSurface = renderer.renderViewer(intro, 96, 28);
- const backdropText = surfaceText(renderedBackdrops[0]);
- const idleSurface = renderer.renderViewer(
- {
- ...intro,
- startupIntroComplete: true,
- frameTimeMs: SLOW_FRAME_MS,
- },
- 96,
- 28,
- );
-
- assert.deepEqual(tracedTimes, [2]);
- assert.notEqual(surfaceText(introSurface), backdropText);
- assert.equal(surfaceText(idleSurface), backdropText);
-});
-
-test("viewer renderer continues tracing while startup browser is open", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const tracedTimes = [];
- const renderer = viewerContent.createViewerContentRenderer(
- tracingTitleRenderer(tracedTimes, "modal"),
- );
- const base = mockTitleScreenModel(titleScreen, {
- time: 1,
- frameTimeMs: FAST_FRAME_MS,
- startupIntroComplete: false,
- });
-
- const live = renderer.renderViewer(base, TITLE_WIDTH, TITLE_HEIGHT);
- const openModal = renderer.renderViewer(
- {
- ...base,
- time: 7,
- startupIntroComplete: true,
- startupFileModalOpen: true,
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
- const typed = renderer.renderViewer(
- {
- ...base,
- time: 8,
- startupIntroComplete: true,
- startupFileModalOpen: true,
- startupFileModalInput: "read",
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
-
- assert.deepEqual(tracedTimes, [1, 7, 8]);
- assert.notEqual(surfaceText(openModal), surfaceText(live));
- assert.notEqual(surfaceText(typed), surfaceText(openModal));
- assert.deepEqual(renderer.titleScenePerformanceFacts(), {
- posture: "live-trace",
- tracesRays: true,
- usesFrozenBackdrop: false,
- retainsBackdrop: true,
- inputLatencyPosture: "animated-title",
- frameBudgetPosture: "within-budget",
- });
-});
-
-test("viewer renderer keeps slow idle title backdrop frozen until state changes", async () => {
- const [viewerContent, titleScreen, governor] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- importDist("app", "workspace", "title-scene-performance-governor.js"),
- ]);
- const tracedTimes = [];
- const renderer = viewerContent.createViewerContentRenderer(
- tracingTitleRenderer(tracedTimes, "idle"),
- );
- const base = mockTitleScreenModel(titleScreen, {
- time: 10,
- frameTimeMs: SLOW_FRAME_MS,
- startupIntroComplete: true,
- startupFileModalOpen: false,
- });
-
- const initial = renderer.renderViewer(base, TITLE_WIDTH, TITLE_HEIGHT);
- const reused = renderer.renderViewer(
- {
- ...base,
- time: 10 + governor.TITLE_SCENE_LOW_RATE_REFRESH_SECONDS / 4,
- frameTimeMs: FAST_FRAME_MS,
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
- const stillReused = renderer.renderViewer(
- {
- ...base,
- time: 10 + governor.TITLE_SCENE_LOW_RATE_REFRESH_SECONDS * 2,
- frameTimeMs: FAST_FRAME_MS,
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
-
- assert.deepEqual(tracedTimes, [10]);
- assert.equal(reused, initial);
- assert.equal(stillReused, initial);
- assert.equal(surfaceText(reused), surfaceText(initial));
- assert.equal(surfaceText(stillReused), surfaceText(initial));
- assert.deepEqual(renderer.titleScenePerformanceFacts(), {
- posture: "low-rate-frozen-backdrop",
- tracesRays: false,
- usesFrozenBackdrop: true,
- retainsBackdrop: false,
- inputLatencyPosture: "low-rate-title",
- frameBudgetPosture: "within-budget",
- });
-});
-
-test("viewer renderer does not reuse a frozen title backdrop after camera input", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const tracedTimes = [];
- const renderer = viewerContent.createViewerContentRenderer(
- tracingTitleRenderer(tracedTimes, "camera"),
- );
- const base = mockTitleScreenModel(titleScreen, {
- time: 12,
- frameTimeMs: SLOW_FRAME_MS,
- startupIntroComplete: true,
- startupFileModalOpen: false,
- });
-
- const initial = renderer.renderViewer(base, TITLE_WIDTH, TITLE_HEIGHT);
- const moved = renderer.renderViewer(
- {
- ...base,
- time: 12.1,
- titleCamera: {
- ...base.titleCamera,
- position: [1, 2.65, 8.5],
- target: [1, 0.78, 0],
- },
- },
- TITLE_WIDTH,
- TITLE_HEIGHT,
- );
-
- assert.deepEqual(tracedTimes, [12, 12.1]);
- assert.notEqual(surfaceText(moved), surfaceText(initial));
-});
-
-function tracingTitleRenderer(tracedTimes, label) {
- return (width, height, time) => {
- tracedTimes.push(time);
- return stringToSurface(
- `${label} trace ${tracedTimes.length} time ${time}`,
- width,
- height,
- );
- };
-}
diff --git a/spec/workspace-title-screen.spec.mjs b/spec/workspace-title-screen.spec.mjs
index d0397274..3c00879e 100644
--- a/spec/workspace-title-screen.spec.mjs
+++ b/spec/workspace-title-screen.spec.mjs
@@ -1,14 +1,11 @@
import assert from "node:assert/strict";
import test from "node:test";
-import { stringToSurface } from "@flyingrobots/bijou";
import {
- hasNotification,
importDist,
mockDeps,
mockI18n,
mockKeyBindingContext,
mockTitleScreenModel,
- notification,
surfaceText,
} from "./workspace-helpers.mjs";
@@ -17,76 +14,12 @@ const STARTUP_MODAL_SCROLL_SELECTED_INDEX = 8;
const STARTUP_MODAL_SCROLLBAR_TRACK_CHAR = "│";
const STARTUP_MODAL_SCROLLBAR_THUMB_CHAR = "█";
-test("title screen number keys switch to a visible legacy render mode", async () => {
- const [keyBindings, titleScreen, viewerContent] = await Promise.all([
- importDist("app", "workspace", "key-bindings.js"),
- importDist("ui", "title-screen.js"),
- importDist("app", "workspace", "viewer-content.js"),
- ]);
- const base = mockTitleScreenModel(titleScreen, {
- startupIntroComplete: true,
- titleRenderMode: titleScreen.TITLE_RENDER_MODE.Braille,
- });
- const [asciiModel] = keyBindings.updateFromKey(
- { key: "2" },
- base,
- mockKeyBindingContext(),
- );
- const [brailleModel] = keyBindings.updateFromKey(
- { key: "1" },
- mockTitleScreenModel(titleScreen, {
- titleRenderMode: titleScreen.TITLE_RENDER_MODE.Ascii,
- }),
- mockKeyBindingContext(),
- );
-
- assert.equal(asciiModel.titleRenderMode, titleScreen.TITLE_RENDER_MODE.Ascii);
- assert.equal(
- asciiModel.titleBackdropKind,
- titleScreen.TITLE_BACKDROP_KIND.LegacyScene,
- );
- assert.equal(
- brailleModel.titleRenderMode,
- titleScreen.TITLE_RENDER_MODE.Braille,
- );
- assert.equal(
- brailleModel.titleBackdropKind,
- titleScreen.TITLE_BACKDROP_KIND.LegacyScene,
- );
- assert.equal(
- hasNotification(asciiModel, "Title shader", "ASCII · Dense"),
- true,
- );
- assert.equal(hasNotification(brailleModel, "Title shader", "Braille"), true);
- assert.equal(
- notification(asciiModel, "Title shader", "ASCII · Dense").placement,
- "LOWER_RIGHT",
- );
- assert.deepEqual(
- notification(asciiModel, "Title shader", "ASCII · Dense").bgToken,
- {
- hex: "#f0f6fc",
- bg: "#0d1117",
- },
- );
- assert.deepEqual(
- notification(asciiModel, "Title shader", "ASCII · Dense").accentToken,
- {
- hex: "#58a6ff",
- bg: "#0d1117",
- },
- );
- const renderer = viewerContent.createViewerContentRenderer();
- const staticTitle = renderer.renderViewer(base, 80, 24);
- const legacyAsciiTitle = renderer.renderViewer(asciiModel, 80, 24);
- assert.notEqual(surfaceText(legacyAsciiTitle), surfaceText(staticTitle));
-});
-
test("tab skips startup intro without opening the file browser", async () => {
- const [keyBindings, titleScreen, viewerContent] = await Promise.all([
+ const [keyBindings, titleScreen, viewerContent, surfaceFill] = await Promise.all([
importDist("app", "workspace", "key-bindings.js"),
importDist("ui", "title-screen.js"),
importDist("app", "workspace", "viewer-content.js"),
+ importDist("app", "workspace", "surface-fill.js"),
]);
const base = mockTitleScreenModel(titleScreen, {
startupIntroComplete: false,
@@ -97,22 +30,11 @@ test("tab skips startup intro without opening the file browser", async () => {
base,
startupFileDrawerAnimationContext(),
);
- const renderOptions = [];
-
- viewerContent.renderViewerWithTitleRenderer(
- opened,
- 44,
- 8,
- (width, height, _time, _theme, options) => {
- renderOptions.push(options);
- return stringToSurface("title trace", width, height);
- },
- );
+ assert.equal(surfaceText(viewerContent.renderViewer(opened, 80, 24)), surfaceText(surfaceFill.renderJimLogoScreen(80, 24, opened.jeditTheme)));
assert.equal(opened.startupIntroComplete, true);
assert.equal(opened.startupFileModalOpen, false);
assert.equal(commands.length, 0);
- assert.equal(renderOptions[0].suppressPresentation, true);
});
test("startup file modal shortcuts defer to the active title command line", async () => {
@@ -152,123 +74,6 @@ test("startup file modal shortcuts defer to the active title command line", asyn
assert.deepEqual(enterCommands, []);
});
-test("title screen m cycles title materials and reports the material name", async () => {
- const [keyBindings, titleScreen, material, sceneLoader, meshes, port] =
- await Promise.all([
- importDist("app", "workspace", "key-bindings.js"),
- importDist("ui", "title-screen.js"),
- importDist("app", "workspace", "title-mesh-materials.js"),
- importDist("adapters", "title-scene-loader.js"),
- importDist("adapters", "workspace-title-meshes.js"),
- importDist("ports", "title-scene-loader.js"),
- ]);
- const scene = await sceneLoader.loadBuiltInTitleScene(
- port.DEFAULT_BUILT_IN_TITLE_SCENE_NAME,
- meshes.loadStartupTitleMeshes(),
- );
- const [first] = keyBindings.updateFromKey(
- { key: "m", ctrl: false, alt: false, shift: false },
- mockTitleScreenModel(titleScreen, { sceneOverride: scene }),
- mockKeyBindingContext(),
- );
- const [second] = keyBindings.updateFromKey(
- { key: "M", ctrl: false, alt: false, shift: true },
- first,
- mockKeyBindingContext(),
- );
- const firstPreset = material.titleMeshMaterialPresetAt(1);
- const secondPreset = material.titleMeshMaterialPresetAt(2);
-
- assert.equal(first.titleMeshMaterialIndex, 1);
- assert.equal(
- first.titleBackdropKind,
- titleScreen.TITLE_BACKDROP_KIND.LegacyScene,
- );
- assert.deepEqual(first.sceneOverride.objects[0].color, firstPreset.color);
- assert.equal(
- hasNotification(first, "Title material", firstPreset.name),
- true,
- );
- assert.equal(second.titleMeshMaterialIndex, 2);
- assert.equal(
- second.titleBackdropKind,
- titleScreen.TITLE_BACKDROP_KIND.LegacyScene,
- );
- assert.deepEqual(second.sceneOverride.objects[0].color, secondPreset.color);
- assert.equal(
- hasNotification(second, "Title material", secondPreset.name),
- true,
- );
-});
-
-test("title screen uses FPS-style camera keys without an editor", async () => {
- const [keyBindings, titleScreen] = await Promise.all([
- importDist("app", "workspace", "key-bindings.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const base = mockTitleScreenModel(titleScreen, {
- titleCamera: fpsTestCamera(),
- });
- const [forward] = keyBindings.updateFromKey(
- { key: "w", ctrl: false, alt: false, shift: false },
- base,
- mockKeyBindingContext(),
- );
- const [left] = keyBindings.updateFromKey(
- { key: "a", ctrl: false, alt: false, shift: false },
- base,
- mockKeyBindingContext(),
- );
- const [jumped] = keyBindings.updateFromKey(
- { key: "space", ctrl: false, alt: false, shift: false },
- base,
- mockKeyBindingContext(),
- );
- const [crouched] = keyBindings.updateFromKey(
- { key: "shift", ctrl: false, alt: false, shift: false },
- base,
- mockKeyBindingContext(),
- );
-
- assert.ok(forward.titleCamera.position[2] < base.titleCamera.position[2]);
- assert.equal(
- forward.titleBackdropKind,
- titleScreen.TITLE_BACKDROP_KIND.LegacyScene,
- );
- assert.ok(left.titleCamera.position[0] < base.titleCamera.position[0]);
- assert.ok(jumped.titleCamera.position[1] > base.titleCamera.position[1]);
- assert.equal(crouched.titleCamera.crouching, true);
-});
-
-test("title screen mouse movement drags the camera look vector", async () => {
- const [mouse, titleScreen] = await Promise.all([
- importDist("app", "workspace", "mouse.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const base = mockTitleScreenModel(titleScreen, {
- titleCamera: fpsTestCamera(),
- });
- const [anchored] = mouse.updateFromMouse(
- titleMouse("press", 10, 10),
- base,
- mockDeps().sourceHighlighter,
- );
- const [rotated] = mouse.updateFromMouse(
- titleMouse("drag", 14, 12),
- anchored,
- mockDeps().sourceHighlighter,
- );
-
- assert.deepEqual(anchored.titleCamera.position, base.titleCamera.position);
- assert.equal(
- anchored.titleBackdropKind,
- titleScreen.TITLE_BACKDROP_KIND.LegacyScene,
- );
- assert.deepEqual(rotated.titleCamera.position, base.titleCamera.position);
- assert.ok(rotated.titleCamera.target[0] > base.titleCamera.target[0]);
- assert.ok(rotated.titleCamera.target[1] < base.titleCamera.target[1]);
-});
-
test("feedback module exposes notification presentation tokens", async () => {
const feedback = await importDist("ui", "feedback.js");
@@ -277,83 +82,6 @@ test("feedback module exposes notification presentation tokens", async () => {
assert.equal(feedback.NotificationPlacements.LowerRight, "LOWER_RIGHT");
});
-test("title screen rejects unknown ASCII palettes instead of surfacing them in toast text", async () => {
- const [keyBindings, titleScreen] = await Promise.all([
- importDist("app", "workspace", "key-bindings.js"),
- importDist("ui", "title-screen.js"),
- ]);
-
- assert.throws(
- () =>
- keyBindings.updateFromKey(
- { key: "2" },
- mockTitleScreenModel(titleScreen, {
- titleRenderMode: titleScreen.TITLE_RENDER_MODE.Braille,
- titleAsciiPalette: "future-palette",
- }),
- mockKeyBindingContext(),
- ),
- (error) => error?.name === "InvalidTitleAsciiPaletteError",
- );
-});
-
-test("period cycles title screen ASCII palettes only when ASCII mode is active without an editor", async () => {
- const [keyBindings, titleScreen] = await Promise.all([
- importDist("app", "workspace", "key-bindings.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const [ignoredModel, ignoredCommands] = keyBindings.updateFromKey(
- { key: "." },
- mockTitleScreenModel(titleScreen, {
- titleRenderMode: titleScreen.TITLE_RENDER_MODE.Braille,
- titleAsciiPalette: titleScreen.TITLE_ASCII_PALETTE.Dense,
- }),
- mockKeyBindingContext(),
- );
- const [firstModel] = keyBindings.updateFromKey(
- { key: "." },
- mockTitleScreenModel(titleScreen, {
- titleRenderMode: titleScreen.TITLE_RENDER_MODE.Ascii,
- titleAsciiPalette: titleScreen.TITLE_ASCII_PALETTE.Dense,
- }),
- mockKeyBindingContext(),
- );
- const [secondModel] = keyBindings.updateFromKey(
- { key: "." },
- firstModel,
- mockKeyBindingContext(),
- );
-
- assert.equal(
- ignoredModel.titleRenderMode,
- titleScreen.TITLE_RENDER_MODE.Braille,
- );
- assert.equal(
- ignoredModel.titleAsciiPalette,
- titleScreen.TITLE_ASCII_PALETTE.Dense,
- );
- assert.equal(ignoredModel.notifications.items.length, 0);
- assert.equal(ignoredCommands.length, 0);
- assert.equal(firstModel.titleRenderMode, titleScreen.TITLE_RENDER_MODE.Ascii);
- assert.equal(
- firstModel.titleAsciiPalette,
- titleScreen.TITLE_ASCII_PALETTE.Minimal,
- );
- assert.equal(
- secondModel.titleAsciiPalette,
- titleScreen.TITLE_ASCII_PALETTE.Technical,
- );
- assert.equal(hasNotification(firstModel, "ASCII palette", "Minimal"), true);
- assert.equal(
- hasNotification(secondModel, "ASCII palette", "Technical"),
- true,
- );
- assert.equal(
- notification(firstModel, "ASCII palette", "Minimal").placement,
- "LOWER_RIGHT",
- );
-});
-
test("enter skips title intro without opening the startup file browser", async () => {
const [keyBindings, titleScreen] = await Promise.all([
importDist("app", "workspace", "key-bindings.js"),
@@ -416,149 +144,6 @@ test("escape dismisses open startup file browser with a drawer animation", async
assert.equal(commands.length, 1);
});
-test("startup file browser keeps tracing the title scene while input changes", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const tracedTimes = [];
- const titleRenderer = (width, height, time) => {
- tracedTimes.push(time);
- return stringToSurface(
- `trace ${tracedTimes.length} time ${time}`,
- width,
- height,
- );
- };
- const base = mockTitleScreenModel(titleScreen, {
- time: 1,
- startupIntroComplete: false,
- startupFileModalOpen: false,
- });
- const renderer = viewerContent.createViewerContentRenderer(titleRenderer);
-
- const live = renderer.renderViewer(base, 44, 6);
- const openModal = renderer.renderViewer(
- {
- ...base,
- time: 7,
- startupIntroComplete: true,
- startupFileModalOpen: true,
- },
- 44,
- 6,
- );
- const typed = renderer.renderViewer(
- {
- ...base,
- time: 8,
- startupIntroComplete: true,
- startupFileModalOpen: true,
- startupFileModalInput: "read",
- },
- 44,
- 6,
- );
-
- assert.deepEqual(tracedTimes, [1, 7, 8]);
- assert.notEqual(surfaceText(openModal), surfaceText(live));
- assert.notEqual(surfaceText(typed), surfaceText(openModal));
-});
-
-test("startup file browser traces every frame when no title cache exists", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const tracedTimes = [];
- const titleRenderer = (width, height, time) => {
- tracedTimes.push(time);
- return stringToSurface(
- `fallback ${tracedTimes.length} time ${time}`,
- width,
- height,
- );
- };
- const model = mockTitleScreenModel(titleScreen, {
- time: 7,
- startupIntroComplete: true,
- startupFileModalOpen: true,
- });
- const renderer = viewerContent.createViewerContentRenderer(titleRenderer);
-
- const first = renderer.renderViewer(model, 44, 6);
- const second = renderer.renderViewer(
- {
- ...model,
- time: 8,
- startupFileModalInput: "r",
- },
- 44,
- 6,
- );
-
- assert.deepEqual(tracedTimes, [7, 8]);
- assert.notEqual(surfaceText(second), surfaceText(first));
-});
-
-test("startup file browser title renderer state is isolated per renderer instance", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const firstRenderer = viewerContent.createViewerContentRenderer(
- (width, height) => stringToSurface("session one", width, height),
- );
- const secondRenderer = viewerContent.createViewerContentRenderer(
- (width, height) => stringToSurface("session two", width, height),
- );
- const base = mockTitleScreenModel(titleScreen, {
- time: 1,
- startupIntroComplete: false,
- startupFileModalOpen: false,
- });
-
- firstRenderer.renderViewer(base, 44, 6);
- const second = secondRenderer.renderViewer(
- {
- ...base,
- time: 7,
- startupIntroComplete: true,
- startupFileModalOpen: true,
- },
- 44,
- 6,
- );
-
- assert.match(surfaceText(second), /session two/);
- assert.doesNotMatch(surfaceText(second), /session one/);
-});
-
-test("title renderer receives wall-clock time for day-night sky", async () => {
- const [viewerContent, titleScreen] = await Promise.all([
- importDist("app", "workspace", "viewer-content.js"),
- importDist("ui", "title-screen.js"),
- ]);
- const renderOptions = [];
- const titleRenderer = (width, height, _time, _theme, options) => {
- renderOptions.push(options);
- return stringToSurface("wall clock", width, height);
- };
- const renderer = viewerContent.createViewerContentRenderer(titleRenderer);
-
- renderer.renderViewer(
- mockTitleScreenModel(titleScreen, {
- lastFrameMs: 123456789,
- startupIntroComplete: false,
- startupFileModalOpen: false,
- }),
- 44,
- 6,
- );
-
- assert.equal(renderOptions[0].wallClockMs, 123456789);
-});
-
test("startup intro skip does not interrupt focused file drawer open", async () => {
const [keyBindings, titleScreen, fileSystem] = await Promise.all([
importDist("app", "workspace", "key-bindings.js"),
@@ -1092,45 +677,6 @@ function startupModalOverflowEntries(fileSystem) {
);
}
-function fpsTestCamera() {
- return {
- angle: 0,
- angleTarget: 0,
- angleMotionId: 0,
- radius: 2,
- radiusTarget: 2,
- radiusMotionId: 0,
- position: [0, 1, 0],
- target: [0, 1, -2],
- eyeY: 1,
- crouching: false,
- };
-}
-
-function titleBodyCells(surface) {
- return positionedCells(surface).filter(
- ({ y }) => y >= 2 && y < surface.height - 2,
- );
-}
-
-function isBrailleCell(char) {
- const codePoint = char.codePointAt(0) ?? 0;
- return codePoint >= 0x2800 && codePoint <= 0x28ff;
-}
-
-function titleMouse(action, col, row) {
- return {
- type: "mouse",
- action,
- button: "none",
- col,
- row,
- shift: false,
- alt: false,
- ctrl: false,
- };
-}
-
function startupModalScrollbarCells(surface) {
const thumb = positionedCells(surface).find(
({ cell }) => cell.char === STARTUP_MODAL_SCROLLBAR_THUMB_CHAR,
diff --git a/src/adapters/workspace-app.ts b/src/adapters/workspace-app.ts
index c4cbec1a..e1a90503 100644
--- a/src/adapters/workspace-app.ts
+++ b/src/adapters/workspace-app.ts
@@ -7,7 +7,6 @@ import {
createGraftDiagnosticsPort,
createGraftSourceHighlighter,
} from './graft-source-highlighter.js';
-import { createTitleSceneLoaderPort } from './title-scene-loader.js';
import { createInitialModelSnapshot } from './workspace-initial-model-snapshot.js';
import { createPerfApp } from './workspace-perf-app.js';
import type { WorkspaceProductionTextDependencies } from './workspace-production-text-dependencies.js';
@@ -27,7 +26,6 @@ export interface WorkspaceAppOptions {
perfEnabled: boolean;
profileEnabled?: boolean;
nowMs?: () => number;
- random?: () => number;
seed?: ReturnType;
}
@@ -36,11 +34,9 @@ export function createWorkspaceApp(
productionText: WorkspaceProductionTextDependencies,
) {
const nowMs = options.nowMs ?? (() => Date.now());
- const random = options.random ?? Math.random;
const runtime = createWorkspaceRuntime(workspaceRuntimeDependencies(
options,
nowMs,
- random,
productionText,
));
return createPerfApp(workspaceRuntimeApp(runtime), {
@@ -51,14 +47,12 @@ export function createWorkspaceApp(
function workspaceRuntimeDependencies(
options: WorkspaceAppOptions,
nowMs: () => number,
- random: () => number,
productionText: WorkspaceProductionTextDependencies,
) {
const editorFile = editorFilePort;
const graftDiagnostics = createGraftDiagnosticsPort();
const graftSession = createGraftSessionPort();
const sourceHighlighter = createGraftSourceHighlighter();
- const titleSceneLoader = createTitleSceneLoaderPort();
return {
initialColumns: options.initialColumns,
initialRows: options.initialRows,
@@ -69,10 +63,9 @@ function workspaceRuntimeDependencies(
productionTextSession: productionText.productionTextSession,
graftSession,
sourceHighlighter,
- titleSceneLoader,
profiler: createRaytracerProfilerPort(nowMs),
profileOnStartup: options.profileEnabled ?? false,
- initialModel: options.seed ?? createInitialModelSnapshot(nowMs(), options.initialWorkingDirectory, random),
+ initialModel: options.seed ?? createInitialModelSnapshot(nowMs(), options.initialWorkingDirectory),
nowMs,
openEntry: (
model: WorkspaceModel,
diff --git a/src/adapters/workspace-initial-model-snapshot.ts b/src/adapters/workspace-initial-model-snapshot.ts
index 7167470c..251d7fed 100644
--- a/src/adapters/workspace-initial-model-snapshot.ts
+++ b/src/adapters/workspace-initial-model-snapshot.ts
@@ -1,28 +1,17 @@
import { BijouI18nAdapter } from "./bijou-i18n-adapter.js";
import type { WorkspaceInitialModelSnapshot } from "../app/workspace/init.js";
-import type { TitleMeshLibrary } from "../ui/title-mesh-library.js";
import {
JEDIT_THEME_ENV,
resolveInitialJeditTheme,
} from "../ui/jedit-themes.js";
import { loadEntries } from "./filesystem.js";
-// Startup loads no title geometry. The ray-traced title subsystem parses
-// ~1.75 MB of OBJ text (teapot, dragon, bunny) and that cost belonged to a
-// title screen jedit no longer shows on launch. The meshes and scenes remain
-// in the tree and load on demand for anything that opts into the legacy
-// backdrop; nothing on the startup path pays for them.
-const NO_STARTUP_TITLE_MESHES: TitleMeshLibrary = Object.freeze({});
-
export function createInitialModelSnapshot(
nowMs: number,
cwd: string,
- random: () => number,
): WorkspaceInitialModelSnapshot {
return {
entries: loadEntries(cwd),
- titleMeshes: NO_STARTUP_TITLE_MESHES,
- titleSceneSeed: random(),
jeditTheme: resolveInitialJeditTheme(process.env[JEDIT_THEME_ENV]),
i18n: new BijouI18nAdapter(),
nowMs,
diff --git a/src/adapters/workspace-perf-app.ts b/src/adapters/workspace-perf-app.ts
index cb9840d2..98008443 100644
--- a/src/adapters/workspace-perf-app.ts
+++ b/src/adapters/workspace-perf-app.ts
@@ -1,7 +1,5 @@
import {
perfOverlaySurface,
- statsPanelSurface,
- type StatsPanelEntry,
type Surface,
} from "@flyingrobots/bijou";
import {
@@ -11,11 +9,6 @@ import {
} from "@flyingrobots/bijou-tui";
import type { WorkspaceModel } from "../app/workspace/model.js";
import type { WorkspaceMsg } from "../app/workspace/msg.js";
-import {
- titleScenePerformanceStats,
- type TitleScenePerformanceStats,
-} from "../app/workspace/title-scene-performance-stats.js";
-
export interface PerfAppOptions {
readonly initialPerfVisible: boolean;
}
@@ -51,20 +44,7 @@ export function createPerfApp(
function perfOverlays(model: WorkspaceModel, memory: NodeJS.MemoryUsage) {
const perfSurface = jeditPerfSurface(model, memory);
- const titleStats = titleScenePerformanceStats(model);
- return [
- { content: "", surface: perfSurface, row: 0, col: 0 },
- ...(titleStats == null
- ? []
- : [
- {
- content: "",
- surface: titleSceneStatsSurface(titleStats),
- row: perfSurface.height,
- col: 0,
- },
- ]),
- ];
+ return [{ content: "", surface: perfSurface, row: 0, col: 0 }];
}
function jeditPerfSurface(
@@ -87,24 +67,6 @@ function jeditPerfSurface(
);
}
-function titleSceneStatsSurface(stats: TitleScenePerformanceStats): Surface {
- return statsPanelSurface(titleSceneStatsEntries(stats), {
- title: "title scene",
- width: 32,
- });
-}
-
-function titleSceneStatsEntries(
- stats: TitleScenePerformanceStats,
-): readonly StatsPanelEntry[] {
- return [
- { label: "scene", value: stats.sceneName },
- { label: "objects", value: String(stats.objectCount) },
- { label: "triangles", value: String(stats.triangleCount) },
- { label: "rays", value: String(stats.rayCount) },
- ];
-}
-
function bytesToMegabytes(bytes: number): number {
return bytes / 1024 / 1024;
}
diff --git a/src/app/keybindings.ts b/src/app/keybindings.ts
index ad28f498..ae6afe6b 100644
--- a/src/app/keybindings.ts
+++ b/src/app/keybindings.ts
@@ -4,7 +4,6 @@ export const JEDIT_KEY_ACTION = {
OpenSettings: Symbol('jedit.key.action.open-settings'),
ToggleMarkdownPreview: Symbol('jedit.key.action.toggle-markdown-preview'),
ToggleTheme: Symbol('jedit.key.action.toggle-theme'),
- OpenScenePicker: Symbol('jedit.key.action.open-scene-picker'),
} as const;
export type JeditKeyAction = typeof JEDIT_KEY_ACTION[keyof typeof JEDIT_KEY_ACTION];
@@ -22,7 +21,6 @@ type JeditKeyChord = Pick;
const KEY_F2 = 'f2';
const KEY_F3 = 'f3';
-const KEY_L = 'l';
const KEY_Q = 'q';
const KEY_T = 't';
const MODIFIER_CTRL = 'ctrl';
@@ -37,11 +35,6 @@ export const JEDIT_SETTINGS_CLOSE_KEY = KEY_Q;
export const JEDIT_SETTINGS_CLOSE_LABEL = KEY_Q;
export const JEDIT_MARKDOWN_PREVIEW_TOGGLE_KEY = KEY_F3;
export const JEDIT_MARKDOWN_PREVIEW_TOGGLE_LABEL = KEY_F3;
-export const JEDIT_SCENE_PICKER_TOGGLE_KEY = KEY_L;
-export const JEDIT_SCENE_PICKER_TOGGLE_LABEL = formatJeditKeyLabel({
- key: JEDIT_SCENE_PICKER_TOGGLE_KEY,
- ctrl: true,
-});
export const JEDIT_THEME_TOGGLE_KEY = KEY_T;
export const JEDIT_THEME_TOGGLE_LABEL = formatJeditKeyLabel({
key: JEDIT_THEME_TOGGLE_KEY,
@@ -65,12 +58,6 @@ export const JEDIT_KEY_BINDINGS = ensureUniqueJeditKeyBindings([
label: JEDIT_THEME_TOGGLE_LABEL,
ctrl: true,
},
- {
- action: JEDIT_KEY_ACTION.OpenScenePicker,
- key: JEDIT_SCENE_PICKER_TOGGLE_KEY,
- label: JEDIT_SCENE_PICKER_TOGGLE_LABEL,
- ctrl: true,
- },
] as const);
export function ensureUniqueJeditKeyBindings(
diff --git a/src/app/workspace/command-line.ts b/src/app/workspace/command-line.ts
index dd378b47..cd81ff48 100644
--- a/src/app/workspace/command-line.ts
+++ b/src/app/workspace/command-line.ts
@@ -187,7 +187,6 @@ export function canOpenWorkspaceCommandLine(model: WorkspaceModel): boolean {
model.commandLine?.active !== true &&
!model.quitConfirmOpen &&
!model.settingsOpen &&
- !model.scenePickerOpen &&
!model.startupFileModalOpen
);
}
diff --git a/src/app/workspace/focused-pane-key-bindings.ts b/src/app/workspace/focused-pane-key-bindings.ts
index 10b84514..9e580523 100644
--- a/src/app/workspace/focused-pane-key-bindings.ts
+++ b/src/app/workspace/focused-pane-key-bindings.ts
@@ -10,10 +10,7 @@ import type { WorkspaceMsg } from './msg.js';
type KeyBindingResult = [WorkspaceModel, Cmd[]];
-// A focused drawer owns its own navigation keys. Without this, the title
-// screen's camera bindings claimed the arrow keys whenever no file was open,
-// so arrows did nothing in the explorer and quietly switched on the
-// ray-traced backdrop instead.
+// A focused drawer owns its navigation and Escape handling.
export function workspaceDrawerHasFocus(model: WorkspaceModel): boolean {
return (
(model.focusPane === FocusPanes.Files && model.fileDrawerOpen) ||
diff --git a/src/app/workspace/init.ts b/src/app/workspace/init.ts
index 0ae7d7bd..51a13a6d 100644
--- a/src/app/workspace/init.ts
+++ b/src/app/workspace/init.ts
@@ -1,24 +1,8 @@
-import { createTitleCameraState } from "../title-camera-session.js";
import { createFeedbackState } from "../../ui/feedback.js";
-import {
- titleBunnySceneCameraPlacement,
- titleSceneCameraPlacement,
- type TitleScene,
-} from "../../ui/title-scene.js";
-import {
- TITLE_ASCII_PALETTE,
- TITLE_BACKDROP_KIND,
- TITLE_RENDER_MODE,
-} from "../../ui/title-screen.js";
import { FocusPanes, type FocusPane } from "../../ui/panel-focus.js";
-import type { TitleMeshLibrary } from "../../ui/title-mesh-library.js";
import type { WorkspaceModel } from "./model.js";
import type { WorkspaceMsg } from "./msg.js";
import type { I18nPort } from "../../ports/i18n.js";
-import {
- BUILT_IN_TITLE_SCENE_NAMES,
- type BuiltInTitleSceneName,
-} from "../../ports/title-scene-loader.js";
import type { JeditTheme } from "../../ui/jedit-theme.js";
import type { FileEntry } from "../../ports/file-system.js";
import { ViewModes } from "./view-mode.js";
@@ -38,13 +22,9 @@ const INITIAL_LINE_NUMBER_MODE: WorkspaceModel["lineNumberMode"] =
INITIAL_WORKSPACE_LINE_NUMBER_MODE;
export interface WorkspaceInitialModelSnapshot {
- readonly titleSceneSeed: number;
readonly jeditTheme: JeditTheme;
readonly i18n: I18nPort;
readonly entries: readonly FileEntry[];
- readonly titleMeshes?: TitleMeshLibrary;
- readonly sceneOverride?: TitleScene;
- readonly sceneOverrideName?: BuiltInTitleSceneName;
readonly nowMs: number;
}
@@ -54,8 +34,7 @@ export function createInitialModel(
rows: number,
snapshot: WorkspaceInitialModelSnapshot,
): WorkspaceModel {
- const { titleSceneSeed, jeditTheme, i18n, entries, nowMs } = snapshot;
- const titleMeshes = snapshot.titleMeshes ?? {};
+ const { jeditTheme, i18n, entries, nowMs } = snapshot;
const textRuntimeProfile = TEXT_RUNTIME_PROFILE_ECHO_HOSTED;
return {
i18n,
@@ -80,11 +59,8 @@ export function createInitialModel(
...initialGraftState(),
...initialSourceHighlightState(),
...initialCommandLineFilePreviewState(),
- titleSceneSeed,
- titleMeshes,
columns,
rows,
- ...initialSceneStateFromSnapshot(titleSceneSeed, titleMeshes, snapshot),
...initialRuntimeState(nowMs),
};
}
@@ -164,52 +140,12 @@ function initialRuntimeState(nowMs: number) {
};
}
-function initialSceneStateFromSnapshot(
- titleSceneSeed: number,
- titleMeshes: TitleMeshLibrary,
- snapshot: WorkspaceInitialModelSnapshot,
-) {
- return initialSceneState(
- titleSceneSeed,
- titleMeshes,
- snapshot.sceneOverride,
- snapshot.sceneOverrideName,
- );
-}
-
-function initialSceneState(
- titleSceneSeed: number,
- titleMeshes: TitleMeshLibrary,
- sceneOverride?: TitleScene,
- sceneOverrideName?: BuiltInTitleSceneName,
-) {
- const cameraPlacement =
- sceneOverride?.camera ??
- (titleMeshes.bunny == null
- ? titleSceneCameraPlacement(titleSceneSeed)
- : titleBunnySceneCameraPlacement());
- return {
- scenePickerOpen: false,
- scenePickerFocusIndex: 0,
- availableScenes: BUILT_IN_TITLE_SCENE_NAMES,
- ...(sceneOverrideName == null ? {} : { titleSceneName: sceneOverrideName }),
- ...(sceneOverride == null ? {} : { sceneOverride }),
- titleCamera: createTitleCameraState(cameraPlacement),
- titleMouseLook: undefined,
- titleBackdropKind: TITLE_BACKDROP_KIND.StaticLogo,
- titleRenderMode: TITLE_RENDER_MODE.Braille,
- titleAsciiPalette: TITLE_ASCII_PALETTE.Dense,
- titleMeshMaterialIndex: 0,
- };
-}
-
// Bijou re-renders only when update returns a different model reference, so an
// idle tick must return the model it was given. Rebuilding it on every pulse
// cost a full-surface render 60 times a second to produce identical output.
export function workspaceAnimationIsActive(model: WorkspaceModel): boolean {
return (
!model.startupIntroComplete ||
- model.titleBackdropKind === TITLE_BACKDROP_KIND.LegacyScene ||
model.profiler.active ||
model.perfVisible
);
diff --git a/src/app/workspace/key-binding-context.ts b/src/app/workspace/key-binding-context.ts
index 62e3ca42..646e3214 100644
--- a/src/app/workspace/key-binding-context.ts
+++ b/src/app/workspace/key-binding-context.ts
@@ -4,7 +4,6 @@ import type { FileSystemPort } from '../../ports/file-system.js';
import type { GraftDiagnosticsPort } from '../../ports/graft-diagnostics.js';
import type { GraftSessionPort } from '../../ports/graft-session.js';
import type { SourceHighlighter } from '../../ports/source-highlighter.js';
-import type { TitleSceneLoaderPort } from '../../ports/title-scene-loader.js';
import type { CreateDrawerAnimationCmd } from './drawer.js';
import type { WorkspaceMsg } from './msg.js';
import type { ProductionTextSession } from './production-text-session.js';
@@ -20,7 +19,6 @@ export interface UpdateFromKeyDeps {
readonly sourceHighlighter: SourceHighlighter;
readonly graftDiagnostics: GraftDiagnosticsPort;
readonly graftSession: GraftSessionPort;
- readonly titleSceneLoader: TitleSceneLoaderPort;
readonly productionTextSession: ProductionTextSession;
}
diff --git a/src/app/workspace/key-bindings.ts b/src/app/workspace/key-bindings.ts
index 4556d0ad..7644d04b 100644
--- a/src/app/workspace/key-bindings.ts
+++ b/src/app/workspace/key-bindings.ts
@@ -10,7 +10,6 @@ import type { WorkspaceKeyBindingContext } from './key-binding-context.js';
import type { WorkspaceModel } from './model.js';
import type { WorkspaceMsg } from './msg.js';
import { clearWorkspaceInlinePanelAfterKey } from './workspace-inline-panel.js';
-import { updateScenePickerKey } from './scene-picker-key-bindings.js';
import { updateSettingsKey } from './settings-key-bindings.js';
import { updateStartupFileModalKey } from './startup-file-modal-key-bindings.js';
import { updateTitleScreenKey } from './title-screen-key-bindings.js';
@@ -42,7 +41,6 @@ function updateWorkspaceOverlayKey(
) {
return updateStartupFileModalKey(msg, model, context)
?? updateSettingsKey(msg, model, context)
- ?? updateScenePickerKey(msg, model, context)
?? updateCommandLineKey(msg, model, context)
- ?? updateTitleScreenKey(msg, model, context);
+ ?? updateTitleScreenKey(msg, model);
}
diff --git a/src/app/workspace/model.ts b/src/app/workspace/model.ts
index 708e19a4..618bf281 100644
--- a/src/app/workspace/model.ts
+++ b/src/app/workspace/model.ts
@@ -3,23 +3,11 @@ import type { FileEntry } from "../../ports/file-system.js";
import type { GraftDiagnosticsReport } from "../../ports/graft-diagnostics.js";
import type { GraftInfo } from "../../ports/graft-session.js";
import type { I18nPort } from "../../ports/i18n.js";
-import type { BuiltInTitleSceneName } from "../../ports/title-scene-loader.js";
import type { SourceHighlightReading } from "../../ports/source-highlighter.js";
import type { JeditTheme } from "../../ui/jedit-theme.js";
import type { SourceLineNumberMode } from "../../ui/source-line-number-mode.js";
import type { FocusPane } from "../../ui/panel-focus.js";
-import type { TitleMeshLibrary } from "../../ui/title-mesh-library.js";
-import type { TitleScene } from "../../ui/title-scene.js";
-import type {
- TitleAsciiPalette,
- TitleBackdropKind,
- TitleRenderMode,
-} from "../../ui/title-screen.js";
import type { ProfilerState } from "../raytracer-profiler.js";
-import type {
- TitleCameraMouseLookPointer,
- TitleCameraState,
-} from "../title-camera-session.js";
import type { TextRuntimeProfile } from "../text-runtime-profile.js";
import type { EditorState } from "./editor/model.js";
import type { WorkspaceMsg } from "./msg.js";
@@ -77,13 +65,6 @@ export interface WorkspaceModel
readonly sourceHighlight?: SourceHighlightReading;
readonly sourceHighlightLoading: boolean;
readonly sourceHighlightRequestId: number;
- readonly titleSceneSeed: number;
- readonly titleMeshes: TitleMeshLibrary;
- readonly scenePickerOpen: boolean;
- readonly scenePickerFocusIndex: number;
- readonly availableScenes: readonly BuiltInTitleSceneName[];
- readonly titleSceneName?: BuiltInTitleSceneName;
- readonly sceneOverride?: TitleScene;
readonly columns: number;
readonly rows: number;
readonly time: number;
@@ -91,12 +72,6 @@ export interface WorkspaceModel
readonly lastFrameMs: number;
readonly frameTimeMs: number;
readonly frameTimeHistory: readonly number[];
- readonly titleCamera: TitleCameraState;
- readonly titleMouseLook?: TitleCameraMouseLookPointer;
- readonly titleBackdropKind: TitleBackdropKind;
- readonly titleRenderMode: TitleRenderMode;
- readonly titleAsciiPalette: TitleAsciiPalette;
- readonly titleMeshMaterialIndex: number;
readonly profiler: ProfilerState;
readonly commandLine: WorkspaceCommandLineState;
readonly inlinePanel?: WorkspaceInlinePanel;
diff --git a/src/app/workspace/mouse.ts b/src/app/workspace/mouse.ts
index 0309bba4..3c0e507d 100644
--- a/src/app/workspace/mouse.ts
+++ b/src/app/workspace/mouse.ts
@@ -4,7 +4,6 @@ import {
scrollIndexByRows,
scrollTextViewport,
} from '../../ui/mouse-scroll.js';
-import { updateTitleCameraFromMouseLook } from '../title-camera-session.js';
import { moveSettingsFocusIndex } from '../settings-session.js';
import type { WorkspaceModel } from './model.js';
import type { WorkspaceMsg } from './msg.js';
@@ -20,9 +19,7 @@ import {
WORKSPACE_BODY_TOP_OFFSET,
} from './viewport.js';
import { FocusPanes } from '../../ui/panel-focus.js';
-import { workspaceDrawerHasFocus } from './focused-pane-key-bindings.js';
import { beginWorkspaceSourceHighlightRefresh } from './workspace-source-highlight.js';
-import { TITLE_BACKDROP_KIND } from '../../ui/title-screen.js';
const DRAWER_PAD_MULTIPLIER = 2;
const MOUSE_PRESS = 'press';
@@ -52,7 +49,7 @@ export function updateFromMouse(
}
const deltaRows = mouseScrollDeltaRows(msg);
if (deltaRows === 0) {
- return updateTitleCameraFromMouse(msg, model);
+ return [model, []];
}
const drawer = updateScrollableDrawerFromMouse(model, deltaRows);
if (drawer != null) {
@@ -123,44 +120,6 @@ function openClickedEntry(
return opener.openEntry(model, entry);
}
-function updateTitleCameraFromMouse(
- msg: MouseMsg,
- model: WorkspaceModel,
-): [WorkspaceModel, Cmd[]] {
- if (!titleMouseLookEnabled(model)) {
- return [model, []];
- }
- const result = updateTitleCameraFromMouseLook(
- { col: msg.col, row: msg.row },
- model.titleCamera,
- model.titleMouseLook,
- );
- return [
- {
- ...model,
- titleCamera: result.state,
- titleMouseLook: result.pointer,
- titleBackdropKind: TITLE_BACKDROP_KIND.LegacyScene,
- },
- [],
- ];
-}
-
-// Mouse-look activates the ray-traced backdrop, so it must not trigger while a
-// drawer holds focus. Moving the pointer over the file explorer used to switch
-// the ray tracer on without the reader asking for it.
-function titleMouseLookEnabled(model: WorkspaceModel): boolean {
- return (
- model.editor == null &&
- !workspaceDrawerHasFocus(model) &&
- !model.settingsOpen &&
- !model.scenePickerOpen &&
- !model.startupFileModalOpen &&
- !model.quitConfirmOpen &&
- !model.commandLine.active
- );
-}
-
function updateScrollableDrawerFromMouse(model: WorkspaceModel, deltaRows: number): WorkspaceModel | undefined {
if (model.settingsOpen) {
return {
diff --git a/src/app/workspace/msg.ts b/src/app/workspace/msg.ts
index 232d12cf..66f5b733 100644
--- a/src/app/workspace/msg.ts
+++ b/src/app/workspace/msg.ts
@@ -1,12 +1,9 @@
import type { RuntimeIssue } from "@flyingrobots/bijou-tui";
import type { GraftDiagnosticsReport } from "../../ports/graft-diagnostics.js";
import type { GraftInfo } from "../../ports/graft-session.js";
-import type { BuiltInTitleSceneName } from "../../ports/title-scene-loader.js";
import type { DrawerKind } from "../../ui/drawer-layout.js";
-import type { TitleScene } from "../../ui/title-scene.js";
import type { ProfilerMsg } from "../raytracer-profiler.js";
import type { SourceHighlightMsg } from "../source-highlight-session.js";
-import type { TitleCameraMotionMsg } from "../title-camera-session.js";
import type { JeditWhyRangeReport } from "../../ports/jedit-why-range.js";
import type { JeditWhyReport } from "./command-provenance.js";
import type { ProductionTextObstruction } from "./production-text-session.js";
@@ -28,7 +25,6 @@ import type { WorkspaceBufferCausalLineChanges } from "./workspace-causal-line-c
const WORKSPACE_MESSAGE_DRAWER_PROGRESS = "drawer-progress";
const WORKSPACE_MESSAGE_GRAFT_DIAGNOSTICS = "graft-diagnostics";
const WORKSPACE_MESSAGE_GRAFT_INFO = "graft-info";
-const WORKSPACE_MESSAGE_LOAD_SCENE_RESULT = "load-scene-result";
const WORKSPACE_MESSAGE_TOGGLE_PROFILER = "toggle-profiler";
const WORKSPACE_MESSAGE_NOTIFICATION_TICK = "notification-tick";
const WORKSPACE_MESSAGE_TIME_TICK = "time-tick";
@@ -57,7 +53,6 @@ export const WorkspaceMessageTypes = Object.freeze({
DrawerProgress: WORKSPACE_MESSAGE_DRAWER_PROGRESS,
GraftDiagnostics: WORKSPACE_MESSAGE_GRAFT_DIAGNOSTICS,
GraftInfo: WORKSPACE_MESSAGE_GRAFT_INFO,
- LoadSceneResult: WORKSPACE_MESSAGE_LOAD_SCENE_RESULT,
ToggleProfiler: WORKSPACE_MESSAGE_TOGGLE_PROFILER,
NotificationTick: WORKSPACE_MESSAGE_NOTIFICATION_TICK,
TimeTick: WORKSPACE_MESSAGE_TIME_TICK,
@@ -116,15 +111,9 @@ export type WorkspaceMsg =
requestId: number;
info: GraftInfo;
}
- | {
- type: typeof WorkspaceMessageTypes.LoadSceneResult;
- scene: TitleScene | undefined;
- sceneName?: BuiltInTitleSceneName;
- }
| ProfilerMsg
| { type: typeof WorkspaceMessageTypes.ToggleProfiler }
| SourceHighlightMsg
- | TitleCameraMotionMsg
| { type: typeof WorkspaceMessageTypes.NotificationTick; atMs: number }
| { type: typeof WorkspaceMessageTypes.TimeTick; time: number }
| { type: typeof WorkspaceMessageTypes.TogglePerf }
diff --git a/src/app/workspace/runtime.ts b/src/app/workspace/runtime.ts
index 1f130e19..de5defe0 100644
--- a/src/app/workspace/runtime.ts
+++ b/src/app/workspace/runtime.ts
@@ -9,10 +9,6 @@ import {
reduceSourceHighlightMsg,
SOURCE_HIGHLIGHT_MESSAGE,
} from "../source-highlight-session.js";
-import {
- reduceTitleCameraMotion,
- TITLE_CAMERA_MESSAGE,
-} from "../title-camera-session.js";
import { ensureEditorVisible, editorViewport } from "./editor-session.js";
import { updateFromKey } from "./key-bindings.js";
import { updateFromMouse } from "./mouse.js";
@@ -39,7 +35,6 @@ import {
applyStartupIntroTime,
applyWorkspaceCausalLineChangeResult,
applyWorkspaceTextMessage,
- applyWorkspaceTitleSceneLoadResult,
applyWorkspaceWhyRangeResult,
syncActiveWorkspaceBufferRecord,
} from "./workspace-state-reducers.js";
@@ -224,15 +219,7 @@ function updateWorkspaceStateMessage(
if (text != null) {
return text;
}
- return msg.type === TITLE_CAMERA_MESSAGE.Frame
- ? [
- {
- ...model,
- titleCamera: reduceTitleCameraMotion(model.titleCamera, msg),
- },
- [],
- ]
- : undefined;
+ return undefined;
}
function updateGeneratedStateMessage(
@@ -242,9 +229,6 @@ function updateGeneratedStateMessage(
if (msg.type === WorkspaceMessageTypes.CausalLineChangesResult) {
return applyWorkspaceCausalLineChangeResult(msg, model);
}
- if (msg.type === WorkspaceMessageTypes.LoadSceneResult) {
- return [applyWorkspaceTitleSceneLoadResult(model, msg), []];
- }
if (msg.type === SOURCE_HIGHLIGHT_MESSAGE) {
return [reduceSourceHighlightMsg(model, msg), []];
}
@@ -439,7 +423,6 @@ function workspaceKeyDeps(deps: WorkspaceRuntimeDependencies) {
sourceHighlighter: deps.sourceHighlighter,
graftSession: deps.graftSession,
graftDiagnostics: deps.graftDiagnostics,
- titleSceneLoader: deps.titleSceneLoader,
productionTextSession: deps.productionTextSession,
},
};
diff --git a/src/app/workspace/scene-picker-key-bindings.ts b/src/app/workspace/scene-picker-key-bindings.ts
deleted file mode 100644
index 1c763884..00000000
--- a/src/app/workspace/scene-picker-key-bindings.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import type { Cmd, KeyMsg } from "@flyingrobots/bijou-tui";
-import { JEDIT_SCENE_PICKER_TOGGLE_KEY } from "../keybindings.js";
-import type { WorkspaceModel } from "./model.js";
-import { WorkspaceMessageTypes, type WorkspaceMsg } from "./msg.js";
-import {
- RuntimeIssueLevels,
- RuntimeIssueSources,
- WorkspaceRuntimeIssueNames,
- WorkspaceRuntimeIssueTypes,
-} from "./runtime-issue.js";
-import {
- isWorkspaceScenePickerAcceptKey,
- isWorkspaceScenePickerCloseKey,
- isWorkspaceScenePickerNextKey,
- isWorkspaceScenePickerPreviousKey,
-} from "./workspace-key.js";
-import type { WorkspaceKeyBindingContext } from "./key-binding-context.js";
-import { TITLE_BACKDROP_KIND } from "../../ui/title-screen.js";
-
-const SCENE_PICKER_MIN_INDEX = 0;
-const SCENE_PICKER_STEP = 1;
-const UNKNOWN_SCENE_LOAD_FAILURE = "Unable to describe scene load failure";
-
-type KeyBindingResult = [WorkspaceModel, Cmd[]];
-
-export function updateScenePickerKey(
- msg: KeyMsg,
- model: WorkspaceModel,
- context: WorkspaceKeyBindingContext,
-): KeyBindingResult | undefined {
- if (
- model.editor == null &&
- msg.ctrl &&
- !msg.alt &&
- msg.key === JEDIT_SCENE_PICKER_TOGGLE_KEY
- ) {
- return [
- {
- ...model,
- scenePickerOpen: !model.scenePickerOpen,
- titleBackdropKind: TITLE_BACKDROP_KIND.LegacyScene,
- },
- [],
- ];
- }
-
- if (!model.scenePickerOpen) {
- return undefined;
- }
-
- return updateOpenScenePickerKey(msg, model, context);
-}
-
-function updateOpenScenePickerKey(
- msg: KeyMsg,
- model: WorkspaceModel,
- context: WorkspaceKeyBindingContext,
-): KeyBindingResult {
- if (isWorkspaceScenePickerCloseKey(msg)) {
- return [{ ...model, scenePickerOpen: false }, []];
- }
- if (isWorkspaceScenePickerPreviousKey(msg)) {
- return [{ ...model, scenePickerFocusIndex: previousSceneIndex(model) }, []];
- }
- if (isWorkspaceScenePickerNextKey(msg)) {
- return [{ ...model, scenePickerFocusIndex: nextSceneIndex(model) }, []];
- }
- if (!isWorkspaceScenePickerAcceptKey(msg)) {
- return [model, []];
- }
- return acceptScenePickerSelection(model, context);
-}
-
-function previousSceneIndex(model: WorkspaceModel): number {
- return Math.max(
- SCENE_PICKER_MIN_INDEX,
- model.scenePickerFocusIndex - SCENE_PICKER_STEP,
- );
-}
-
-function nextSceneIndex(model: WorkspaceModel): number {
- const maxIndex = Math.max(
- SCENE_PICKER_MIN_INDEX,
- model.availableScenes.length - SCENE_PICKER_STEP,
- );
- return Math.min(maxIndex, model.scenePickerFocusIndex + SCENE_PICKER_STEP);
-}
-
-function acceptScenePickerSelection(
- model: WorkspaceModel,
- context: WorkspaceKeyBindingContext,
-): KeyBindingResult {
- const selected = model.availableScenes[model.scenePickerFocusIndex];
- if (selected == null) {
- return [model, []];
- }
-
- return [
- { ...model, scenePickerOpen: false },
- [
- async () => {
- try {
- const scene =
- await context.deps.titleSceneLoader.loadBuiltInTitleScene(
- selected,
- model.titleMeshes,
- );
- return {
- type: WorkspaceMessageTypes.LoadSceneResult,
- scene,
- sceneName: selected,
- };
- } catch (error) {
- const issue =
- error instanceof Error
- ? describeSceneLoadError(error, context.nowMs())
- : describeSceneLoadFailure(String(error), context.nowMs());
- return { type: WorkspaceMessageTypes.RuntimeIssue, issue };
- }
- },
- ],
- ];
-}
-
-function describeSceneLoadError(error: Error, atMs: number) {
- return {
- type: WorkspaceRuntimeIssueTypes.System,
- name: WorkspaceRuntimeIssueNames.SceneLoadError,
- message: error.message,
- level: RuntimeIssueLevels.Error,
- source: RuntimeIssueSources.Command,
- atMs,
- ...(error.stack != null ? { stack: error.stack } : {}),
- };
-}
-
-function describeSceneLoadFailure(
- encodedError: string | undefined,
- atMs: number,
-) {
- return {
- type: WorkspaceRuntimeIssueTypes.System,
- name: WorkspaceRuntimeIssueNames.SceneLoadError,
- message: encodedError ?? UNKNOWN_SCENE_LOAD_FAILURE,
- level: RuntimeIssueLevels.Error,
- source: RuntimeIssueSources.Command,
- atMs,
- };
-}
diff --git a/src/app/workspace/startup-file-modal.ts b/src/app/workspace/startup-file-modal.ts
index fb56f513..f8c33a7f 100644
--- a/src/app/workspace/startup-file-modal.ts
+++ b/src/app/workspace/startup-file-modal.ts
@@ -20,7 +20,9 @@ const STARTUP_FILE_MODAL_MIN_SELECTION = 0;
export function initialStartupFileModalState(): StartupFileModalState {
return {
- startupIntroComplete: false,
+ // The title is a static logo: there is no intro to play, so startup begins
+ // complete and idle ticks do not keep the workspace rendering.
+ startupIntroComplete: true,
startupFileModalOpen: false,
startupFileDrawerProgress: 0,
startupFileModalInput: "",
@@ -39,7 +41,6 @@ export function isStartupIntroSkipCandidate(model: WorkspaceModel): boolean {
model.editor == null &&
model.focusPane === FocusPanes.Editor &&
model.commandLine?.active !== true &&
- !model.scenePickerOpen &&
!model.settingsOpen &&
!model.startupIntroComplete &&
!model.startupFileModalOpen
@@ -53,7 +54,6 @@ export function isStartupFileModalReopenCandidate(
model.editor == null &&
model.focusPane === FocusPanes.Editor &&
model.commandLine?.active !== true &&
- !model.scenePickerOpen &&
!model.settingsOpen &&
!model.quitConfirmOpen &&
model.startupIntroComplete &&
diff --git a/src/app/workspace/surface-fill.ts b/src/app/workspace/surface-fill.ts
index 8939e0d6..3b6afee1 100644
--- a/src/app/workspace/surface-fill.ts
+++ b/src/app/workspace/surface-fill.ts
@@ -29,10 +29,6 @@ export function applyBackground(surface: Surface, token: JeditStyleToken): void
}
}
-// Re-exported here so the viewer reaches every surface it paints through one
-// module. Importing jim-logo-screen directly puts viewer-content.ts over the
-// twelve-import limit, and routing it through ui/title-screen.ts -- the other
-// module that already re-exports viewer surfaces -- would touch a path under
-// the title-scene freeze leash for a re-export that adds no title-scene
-// behaviour. The leash is right to refuse that; this avoids asking.
+// Shared viewer surface helpers include the static logo without importing the
+// standalone title-scene engine.
export { renderJimLogoScreen } from '../../ui/jim-logo-screen.js';
diff --git a/src/app/workspace/title-braille-sampling.ts b/src/app/workspace/title-braille-sampling.ts
deleted file mode 100644
index 8b452fe5..00000000
--- a/src/app/workspace/title-braille-sampling.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import {
- brailleSampleActivityRatio,
- brailleSampleRayPressureRatio,
- type BrailleSampleFrameStats,
- type BrailleTraceBudget,
-} from "../../ui/averaging-braille-canvas.js";
-import type { TitleMesh } from "../../ui/title-mesh.js";
-import type { TitleScene } from "../../ui/title-scene.js";
-import type { WorkspaceModel } from "./model.js";
-
-export interface TitleBrailleTraceBudgetInput {
- readonly frameIndex: number;
- readonly frameTimeMs: number;
- readonly previousStats?: BrailleSampleFrameStats;
- readonly previousPhaseCount?: number;
- readonly cameraMoving?: boolean;
-}
-
-export interface TitleBrailleSampleCacheIdentity {
- readonly stableKey: string;
- readonly sceneOverride?: TitleScene;
- readonly mesh?: TitleMesh;
-}
-
-const TITLE_BRAILLE_FULL_PHASE_COUNT = 1;
-const TITLE_BRAILLE_HALF_PHASE_COUNT = 2;
-const TITLE_BRAILLE_QUARTER_PHASE_COUNT = 4;
-const TITLE_BRAILLE_MOTION_PHASE_COUNT = 8;
-const TITLE_BRAILLE_FRAME_BUDGET_MS = 33;
-const TITLE_BRAILLE_HEAVY_FRAME_MS = 66;
-const TITLE_BRAILLE_ACTIVE_SCREEN_RATIO = 0.2;
-const TITLE_BRAILLE_RAY_PRESSURE_RATIO = 0.2;
-const IDENTITY_PART_SEPARATOR = "|";
-
-export function titleBrailleTraceBudget(
- input: TitleBrailleTraceBudgetInput,
-): BrailleTraceBudget {
- const phaseCount = titleBrailleTracePhaseCount(input);
- return {
- phase:
- phaseCount === TITLE_BRAILLE_FULL_PHASE_COUNT
- ? 0
- : input.frameIndex % phaseCount,
- phaseCount,
- };
-}
-
-export function titleBrailleSampleCacheIdentity(
- model: WorkspaceModel,
- width: number,
- height: number,
-): TitleBrailleSampleCacheIdentity {
- return {
- stableKey: titleBrailleSampleCacheStableKey(model, width, height),
- sceneOverride: model.sceneOverride,
- mesh: model.titleMeshes.bunny,
- };
-}
-
-export function sameTitleBrailleSampleCacheIdentity(
- left: TitleBrailleSampleCacheIdentity,
- right: TitleBrailleSampleCacheIdentity,
-): boolean {
- return (
- left.stableKey === right.stableKey &&
- left.sceneOverride === right.sceneOverride &&
- left.mesh === right.mesh
- );
-}
-
-function titleBrailleTracePhaseCount(
- input: TitleBrailleTraceBudgetInput,
-): number {
- if (!isTitleBraillePressureScreen(input.previousStats)) {
- return TITLE_BRAILLE_FULL_PHASE_COUNT;
- }
- if (input.cameraMoving === true) {
- return TITLE_BRAILLE_FULL_PHASE_COUNT;
- }
- if (isTitleBraillePressureFrame(input)) {
- return input.frameTimeMs > TITLE_BRAILLE_HEAVY_FRAME_MS
- ? TITLE_BRAILLE_QUARTER_PHASE_COUNT
- : TITLE_BRAILLE_HALF_PHASE_COUNT;
- }
- return isReducedTitleBraillePhaseCount(input.previousPhaseCount)
- ? input.previousPhaseCount
- : TITLE_BRAILLE_FULL_PHASE_COUNT;
-}
-
-function isTitleBraillePressureFrame(
- input: TitleBrailleTraceBudgetInput,
-): boolean {
- return input.frameTimeMs > TITLE_BRAILLE_FRAME_BUDGET_MS;
-}
-
-function isTitleBraillePressureScreen(
- stats: BrailleSampleFrameStats | undefined,
-): boolean {
- const rayPressureRatio =
- stats == null ? undefined : brailleSampleRayPressureRatio(stats);
- if (rayPressureRatio != null) {
- return rayPressureRatio >= TITLE_BRAILLE_RAY_PRESSURE_RATIO;
- }
- return (
- stats != null &&
- brailleSampleActivityRatio(stats) >= TITLE_BRAILLE_ACTIVE_SCREEN_RATIO
- );
-}
-
-function isReducedTitleBraillePhaseCount(
- phaseCount: number | undefined,
-): phaseCount is 2 | 4 | 8 {
- return (
- phaseCount === TITLE_BRAILLE_HALF_PHASE_COUNT ||
- phaseCount === TITLE_BRAILLE_QUARTER_PHASE_COUNT ||
- phaseCount === TITLE_BRAILLE_MOTION_PHASE_COUNT
- );
-}
-
-function titleBrailleSampleCacheStableKey(
- model: WorkspaceModel,
- width: number,
- height: number,
-): string {
- return [
- width,
- height,
- model.jeditTheme.name,
- model.titleRenderMode,
- model.titleSceneSeed,
- model.titleSceneName ?? "",
- ...model.titleCamera.position,
- ...model.titleCamera.target,
- ]
- .map(identityPart)
- .join(IDENTITY_PART_SEPARATOR);
-}
-
-function identityPart(part: string | number): string {
- return String(part);
-}
diff --git a/src/app/workspace/title-mesh-materials.ts b/src/app/workspace/title-mesh-materials.ts
deleted file mode 100644
index a77926ce..00000000
--- a/src/app/workspace/title-mesh-materials.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import {
- type TitleScene,
- type TitleSceneColor,
- type TitleSceneObject,
-} from "../../ui/title-scene.js";
-
-export interface TitleMeshMaterialPreset {
- readonly name: string;
- readonly color: TitleSceneColor;
- readonly reflectivity: number;
- readonly transparency: number;
- readonly refractiveIndex: number;
-}
-
-const TITLE_MESH_MATERIAL_OBJECT_LABELS = new Set([
- "stanford-bunny",
- "title-primary-mesh",
-]);
-const FIRST_TITLE_MESH_MATERIAL_INDEX = 0;
-const NEXT_TITLE_MESH_MATERIAL_STEP = 1;
-const DEFAULT_TITLE_MESH_MATERIAL_PRESET = {
- name: "Prismatic Ice",
- color: [194, 236, 255],
- reflectivity: 0.24,
- transparency: 0.62,
- refractiveIndex: 1.65,
-} satisfies TitleMeshMaterialPreset;
-
-export const TITLE_MESH_MATERIAL_PRESETS = [
- DEFAULT_TITLE_MESH_MATERIAL_PRESET,
- {
- name: "Chrome Mirror",
- color: [230, 236, 244],
- reflectivity: 0.98,
- transparency: 0,
- refractiveIndex: 1,
- },
- {
- name: "Emerald Glass",
- color: [92, 255, 178],
- reflectivity: 0.32,
- transparency: 0.58,
- refractiveIndex: 1.52,
- },
- {
- name: "Ruby Resin",
- color: [255, 92, 128],
- reflectivity: 0.18,
- transparency: 0.34,
- refractiveIndex: 1.47,
- },
- {
- name: "Obsidian",
- color: [44, 40, 58],
- reflectivity: 0.72,
- transparency: 0.04,
- refractiveIndex: 1.3,
- },
- {
- name: "Cyan Hologlass",
- color: [80, 246, 255],
- reflectivity: 0.42,
- transparency: 0.72,
- refractiveIndex: 1.82,
- },
-] satisfies readonly TitleMeshMaterialPreset[];
-
-export function titleMeshMaterialPresetAt(
- index: number,
-): TitleMeshMaterialPreset {
- return (
- TITLE_MESH_MATERIAL_PRESETS[titleMeshMaterialIndex(index)] ??
- DEFAULT_TITLE_MESH_MATERIAL_PRESET
- );
-}
-
-export function nextTitleMeshMaterialIndex(index: number): number {
- return titleMeshMaterialIndex(index + NEXT_TITLE_MESH_MATERIAL_STEP);
-}
-
-export function applyTitleMeshMaterial(
- scene: TitleScene,
- preset: TitleMeshMaterialPreset,
-): TitleScene {
- return {
- ...scene,
- objects: scene.objects.map((object) =>
- isTitleMeshMaterialObject(object)
- ? titleObjectWithMaterial(object, preset)
- : object,
- ),
- };
-}
-
-function titleMeshMaterialIndex(index: number): number {
- const count = TITLE_MESH_MATERIAL_PRESETS.length;
- if (!Number.isFinite(index) || count === 0) {
- return FIRST_TITLE_MESH_MATERIAL_INDEX;
- }
- return ((Math.trunc(index) % count) + count) % count;
-}
-
-function isTitleMeshMaterialObject(
- object: TitleSceneObject,
-): boolean {
- return (
- object.label != null && TITLE_MESH_MATERIAL_OBJECT_LABELS.has(object.label)
- );
-}
-
-function titleObjectWithMaterial(
- object: TitleSceneObject,
- preset: TitleMeshMaterialPreset,
-): TitleSceneObject {
- return {
- ...object,
- color: preset.color,
- reflectivity: preset.reflectivity,
- transparency: preset.transparency,
- refractiveIndex: preset.refractiveIndex,
- };
-}
diff --git a/src/app/workspace/title-scene-performance-governor.ts b/src/app/workspace/title-scene-performance-governor.ts
deleted file mode 100644
index 39a14035..00000000
--- a/src/app/workspace/title-scene-performance-governor.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-const LIVE_TRACE = "live-trace";
-const LOW_RATE_FROZEN_BACKDROP = "low-rate-frozen-backdrop";
-const STATIC_TITLE = "static-title";
-const ANIMATED_TITLE_INPUT = "animated-title";
-const LOW_RATE_INPUT = "low-rate-title";
-const WITHIN_BUDGET = "within-budget";
-const OVER_BUDGET = "over-budget";
-
-export const TITLE_SCENE_FRAME_BUDGET_MS = 33;
-export const TITLE_SCENE_LOW_RATE_REFRESH_SECONDS = 0.5;
-
-export const TITLE_SCENE_RENDER_POSTURE = Object.freeze({
- LiveTrace: LIVE_TRACE,
- LowRateFrozenBackdrop: LOW_RATE_FROZEN_BACKDROP,
- StaticTitle: STATIC_TITLE,
-});
-
-export type TitleSceneRenderPosture =
- | typeof LIVE_TRACE
- | typeof LOW_RATE_FROZEN_BACKDROP
- | typeof STATIC_TITLE;
-
-export type TitleSceneInputLatencyPosture =
- | typeof ANIMATED_TITLE_INPUT
- | typeof LOW_RATE_INPUT
- | typeof STATIC_TITLE;
-
-export type TitleSceneFrameBudgetPosture =
- | typeof WITHIN_BUDGET
- | typeof OVER_BUDGET;
-
-export interface TitleScenePerformanceGovernorInput {
- readonly introActive: boolean;
- readonly idleTitleScreen: boolean;
- readonly frozenBackdropAvailable: boolean;
- readonly lowRateFrozenBackdropActive?: boolean;
- readonly cacheAgeSeconds?: number;
- readonly frameTimeMs: number;
-}
-
-export interface TitleSceneRenderDecision {
- readonly posture: TitleSceneRenderPosture;
- readonly shouldTraceRays: boolean;
- readonly shouldUseFrozenBackdrop: boolean;
- readonly shouldRetainRenderedBackdrop: boolean;
- readonly inputLatencyPosture: TitleSceneInputLatencyPosture;
- readonly frameBudgetPosture: TitleSceneFrameBudgetPosture;
-}
-
-export interface TitleScenePerformanceFacts {
- readonly posture: TitleSceneRenderPosture;
- readonly tracesRays: boolean;
- readonly usesFrozenBackdrop: boolean;
- readonly retainsBackdrop: boolean;
- readonly inputLatencyPosture: TitleSceneInputLatencyPosture;
- readonly frameBudgetPosture: TitleSceneFrameBudgetPosture;
-}
-
-export function governTitleSceneRender(
- input: TitleScenePerformanceGovernorInput,
-): TitleSceneRenderDecision {
- validateGovernorInput(input);
- const frameBudgetPosture = frameBudgetPostureFor(input.frameTimeMs);
- if (shouldReuseLowRateBackdrop(input, frameBudgetPosture)) {
- return frozenBackdropDecision(
- TITLE_SCENE_RENDER_POSTURE.LowRateFrozenBackdrop,
- LOW_RATE_INPUT,
- frameBudgetPosture,
- );
- }
- return liveTraceDecision(frameBudgetPosture);
-}
-
-export function titleScenePerformanceFacts(
- decision: TitleSceneRenderDecision,
-): TitleScenePerformanceFacts {
- return {
- posture: decision.posture,
- tracesRays: decision.shouldTraceRays,
- usesFrozenBackdrop: decision.shouldUseFrozenBackdrop,
- retainsBackdrop: decision.shouldRetainRenderedBackdrop,
- inputLatencyPosture: decision.inputLatencyPosture,
- frameBudgetPosture: decision.frameBudgetPosture,
- };
-}
-
-export function staticTitleScenePerformanceFacts(): TitleScenePerformanceFacts {
- return {
- posture: TITLE_SCENE_RENDER_POSTURE.StaticTitle,
- tracesRays: false,
- usesFrozenBackdrop: false,
- retainsBackdrop: false,
- inputLatencyPosture: STATIC_TITLE,
- frameBudgetPosture: WITHIN_BUDGET,
- };
-}
-
-function liveTraceDecision(
- frameBudgetPosture: TitleSceneFrameBudgetPosture,
-): TitleSceneRenderDecision {
- return {
- posture: TITLE_SCENE_RENDER_POSTURE.LiveTrace,
- shouldTraceRays: true,
- shouldUseFrozenBackdrop: false,
- shouldRetainRenderedBackdrop: true,
- inputLatencyPosture: ANIMATED_TITLE_INPUT,
- frameBudgetPosture,
- };
-}
-
-function frozenBackdropDecision(
- posture: TitleSceneRenderPosture,
- inputLatencyPosture: TitleSceneInputLatencyPosture,
- frameBudgetPosture: TitleSceneFrameBudgetPosture,
-): TitleSceneRenderDecision {
- return {
- posture,
- shouldTraceRays: false,
- shouldUseFrozenBackdrop: true,
- shouldRetainRenderedBackdrop: false,
- inputLatencyPosture,
- frameBudgetPosture,
- };
-}
-
-function shouldReuseLowRateBackdrop(
- input: TitleScenePerformanceGovernorInput,
- frameBudgetPosture: TitleSceneFrameBudgetPosture,
-): boolean {
- return (
- (input.idleTitleScreen || input.introActive) &&
- input.frozenBackdropAvailable &&
- (input.lowRateFrozenBackdropActive === true ||
- frameBudgetPosture === OVER_BUDGET)
- );
-}
-
-function frameBudgetPostureFor(
- frameTimeMs: number,
-): TitleSceneFrameBudgetPosture {
- return frameTimeMs > TITLE_SCENE_FRAME_BUDGET_MS
- ? OVER_BUDGET
- : WITHIN_BUDGET;
-}
-
-function validateGovernorInput(
- input: TitleScenePerformanceGovernorInput,
-): void {
- if (!Number.isFinite(input.frameTimeMs) || input.frameTimeMs < 0) {
- throw new RangeError("frameTimeMs must be a non-negative finite number.");
- }
- if (
- input.cacheAgeSeconds != null &&
- (!Number.isFinite(input.cacheAgeSeconds) || input.cacheAgeSeconds < 0)
- ) {
- throw new RangeError(
- "cacheAgeSeconds must be a non-negative finite number.",
- );
- }
-}
diff --git a/src/app/workspace/title-scene-performance-stats.ts b/src/app/workspace/title-scene-performance-stats.ts
deleted file mode 100644
index 0266040e..00000000
--- a/src/app/workspace/title-scene-performance-stats.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import { resolveWorkspaceLayout } from "../../ui/drawer-layout.js";
-import {
- TITLE_SCENE_SHAPE_KIND,
- generateTitleScene,
-} from "../../ui/title-scene.js";
-import type { TitleScene, TitleSceneObject } from "../../ui/title-scene.js";
-import { titleSceneRenderMaterialColors } from "../../ui/title-screen.js";
-import { TITLE_BACKDROP_KIND, TITLE_RENDER_MODE } from "../../ui/title-screen.js";
-import { ASCII_SAMPLE_COUNT } from "../../ui/averaging-ascii-canvas.js";
-import { BRAILLE_SAMPLE_COUNT } from "../../ui/averaging-braille-canvas.js";
-import type { WorkspaceModel } from "./model.js";
-import { workspaceBodyHeight } from "./viewport.js";
-
-const GENERATED_SCENE_PREFIX = "generated";
-const SCENE_SEED_PRECISION = 3;
-
-export interface TitleScenePerformanceStats {
- readonly sceneName: string;
- readonly objectCount: number;
- readonly triangleCount: number;
- readonly rayCount: number;
-}
-
-interface TitleRenderDimensions {
- readonly width: number;
- readonly height: number;
-}
-
-export function titleScenePerformanceStats(
- model: WorkspaceModel,
-): TitleScenePerformanceStats | undefined {
- if (model.editor != null) {
- return undefined;
- }
- if (model.titleBackdropKind !== TITLE_BACKDROP_KIND.LegacyScene) {
- // Nothing is traced unless the legacy backdrop is selected. Reporting a
- // generated scene's object, triangle and ray counts for a backdrop that
- // was never drawn made the overlay claim rays the renderer never cast.
- return undefined;
- }
- const scene = titleSceneForModel(model);
- const dimensions = titleRenderDimensions(model);
- return {
- sceneName: titleSceneName(model),
- objectCount: scene.objects.length,
- triangleCount: titleSceneTriangleCount(scene),
- rayCount: titleSceneRayCount(dimensions, model.titleRenderMode),
- };
-}
-
-function titleSceneForModel(model: WorkspaceModel): TitleScene {
- return (
- model.sceneOverride ??
- generateTitleScene(
- model.titleSceneSeed,
- titleSceneRenderMaterialColors(model.jeditTheme),
- model.titleMeshes.bunny,
- )
- );
-}
-
-function titleSceneName(model: WorkspaceModel): string {
- return (
- model.titleSceneName ??
- `${GENERATED_SCENE_PREFIX}:${model.titleSceneSeed.toFixed(SCENE_SEED_PRECISION)}`
- );
-}
-
-function titleRenderDimensions(model: WorkspaceModel): TitleRenderDimensions {
- const bodyHeight = workspaceBodyHeight({
- rows: model.rows,
- footerVisible: model.footerVisible,
- });
- const layout = resolveWorkspaceLayout(
- model.columns,
- model.fileDrawerProgress,
- model.graftDrawerProgress,
- );
- return {
- width: layout.viewer.width,
- height: bodyHeight,
- };
-}
-
-function titleSceneTriangleCount(scene: TitleScene): number {
- return scene.objects.reduce(
- (total, object) => total + titleSceneObjectTriangleCount(object),
- 0,
- );
-}
-
-function titleSceneObjectTriangleCount(object: TitleSceneObject): number {
- return object.kind === TITLE_SCENE_SHAPE_KIND.Mesh
- ? object.mesh.triangles.length
- : 0;
-}
-
-function titleSceneRayCount(
- dimensions: TitleRenderDimensions,
- renderMode: WorkspaceModel["titleRenderMode"],
-): number {
- const samplesPerCell =
- renderMode === TITLE_RENDER_MODE.Ascii
- ? ASCII_SAMPLE_COUNT
- : BRAILLE_SAMPLE_COUNT;
- return dimensions.width * dimensions.height * samplesPerCell;
-}
diff --git a/src/app/workspace/title-screen-key-bindings.ts b/src/app/workspace/title-screen-key-bindings.ts
index 2cb626fa..f45f33cd 100644
--- a/src/app/workspace/title-screen-key-bindings.ts
+++ b/src/app/workspace/title-screen-key-bindings.ts
@@ -1,284 +1,16 @@
-import { colorHex, type TokenValue } from "@flyingrobots/bijou";
-import type { Cmd, KeyMsg } from "@flyingrobots/bijou-tui";
-import {
- NotificationPlacements,
- NotificationTones,
- NotificationVariants,
- pushNotificationToast,
-} from "../../ui/feedback.js";
-import {
- nextTitleAsciiPalette,
- TITLE_ASCII_PALETTE,
- TITLE_BACKDROP_KIND,
- TITLE_RENDER_MODE,
- type TitleAsciiPalette,
-} from "../../ui/title-screen.js";
-import { updateTitleCameraFromKey } from "../title-camera-session.js";
-import {
- applyTitleMeshMaterial,
- nextTitleMeshMaterialIndex,
- titleMeshMaterialPresetAt,
-} from "./title-mesh-materials.js";
-import type { WorkspaceKeyBindingContext } from "./key-binding-context.js";
+import type { Cmd, KeyMsg } from '@flyingrobots/bijou-tui';
import { workspaceDrawerHasFocus } from './focused-pane-key-bindings.js';
-import type { WorkspaceModel } from "./model.js";
-import type { WorkspaceMsg } from "./msg.js";
-import { WorkspaceKeys } from "./workspace-key.js";
-
-const TITLE_SHADER_TOAST_TITLE = "Title shader";
-const TITLE_ASCII_PALETTE_TOAST_TITLE = "ASCII palette";
-const TITLE_MESH_MATERIAL_NEEDS_SCENE =
- "Load a scene first (ctrl+l) -- the generated backdrop takes its materials from the theme.";
-const TITLE_MESH_MATERIAL_TOAST_TITLE = "Title material";
-const TITLE_SHADER_BRAILLE_LABEL = "Braille";
-const TITLE_SHADER_ASCII_LABEL = "ASCII";
-const TITLE_ASCII_PALETTE_DENSE_LABEL = "Dense";
-const TITLE_ASCII_PALETTE_MINIMAL_LABEL = "Minimal";
-const TITLE_ASCII_PALETTE_TECHNICAL_LABEL = "Technical";
-const TITLE_ASCII_PALETTE_HATCHING_LABEL = "Hatching";
-const TITLE_ASCII_PALETTE_MATRIX_LABEL = "Matrix";
-const TITLE_ASCII_PALETTE_BLOCKS_LABEL = "Blocks";
-const TITLE_ASCII_PALETTE_DITHER_LABEL = "Dither";
-const FALLBACK_TOAST_FOREGROUND = "#e2e7ec";
-const FALLBACK_TOAST_BACKGROUND = "#0e1116";
-const FALLBACK_TOAST_ACCENT = "#d897ff";
-const UNKNOWN_TITLE_ASCII_PALETTE_MESSAGE = "Unknown TitleAsciiPalette variant";
+import type { WorkspaceModel } from './model.js';
+import type { WorkspaceMsg } from './msg.js';
+import { WorkspaceKeys } from './workspace-key.js';
type KeyBindingResult = [WorkspaceModel, Cmd[]];
-class InvalidTitleAsciiPaletteError extends Error {
- public constructor(palette: string) {
- super(`${UNKNOWN_TITLE_ASCII_PALETTE_MESSAGE}: ${palette}`);
- this.name = "InvalidTitleAsciiPaletteError";
- Object.freeze(this);
- }
-}
-
-export function updateTitleScreenKey(
- msg: KeyMsg,
- model: WorkspaceModel,
- context: WorkspaceKeyBindingContext,
-): KeyBindingResult | undefined {
+export function updateTitleScreenKey(msg: KeyMsg, model: WorkspaceModel): KeyBindingResult | undefined {
if (model.editor != null || workspaceDrawerHasFocus(model)) {
return undefined;
}
-
- return (
- updateTitleRenderKey(msg, model, context) ??
- updateTitleMeshMaterialKey(msg, model, context) ??
- updateTitleEscapeKey(msg, model) ??
- updateTitleCameraKey(msg, model)
- );
-}
-
-function updateTitleRenderKey(
- msg: KeyMsg,
- model: WorkspaceModel,
- context: WorkspaceKeyBindingContext,
-): KeyBindingResult | undefined {
- if (msg.key === WorkspaceKeys.One) {
- return pushTitleScreenToast(
- activateLegacyTitleBackdrop({
- ...model,
- titleRenderMode: TITLE_RENDER_MODE.Braille,
- }),
- TITLE_SHADER_BRAILLE_LABEL,
- context,
- );
- }
- if (msg.key === WorkspaceKeys.Two) {
- return pushTitleScreenToast(
- activateLegacyTitleBackdrop({
- ...model,
- titleRenderMode: TITLE_RENDER_MODE.Ascii,
- }),
- asciiShaderLabel(model),
- context,
- );
- }
- if (
- msg.key !== WorkspaceKeys.Period ||
- model.titleRenderMode !== TITLE_RENDER_MODE.Ascii
- ) {
- return msg.key === WorkspaceKeys.Period ? [model, []] : undefined;
- }
-
- const titleAsciiPalette = nextTitleAsciiPalette(model.titleAsciiPalette);
- return pushAsciiPaletteToast(
- activateLegacyTitleBackdrop({ ...model, titleAsciiPalette }),
- titleAsciiPalette,
- context,
- );
-}
-
-function updateTitleMeshMaterialKey(
- msg: KeyMsg,
- model: WorkspaceModel,
- context: WorkspaceKeyBindingContext,
-): KeyBindingResult | undefined {
- if (!isTitleMeshMaterialKey(msg)) {
- return undefined;
- }
- const titleMeshMaterialIndex = nextTitleMeshMaterialIndex(
- model.titleMeshMaterialIndex,
- );
- const preset = titleMeshMaterialPresetAt(titleMeshMaterialIndex);
- // With no scene loaded there is nothing to apply a material to: the generated
- // backdrop takes its materials from the theme and never reads
- // titleMeshMaterialIndex. Cycling the index anyway used to switch the legacy
- // backdrop on and toast that the preset had been applied, so the reader was
- // told a change had happened that could not have happened.
- if (model.sceneOverride == null) {
- return pushTitleMeshMaterialToast(model, TITLE_MESH_MATERIAL_NEEDS_SCENE, context);
- }
- return pushTitleMeshMaterialToast(
- activateLegacyTitleBackdrop({
- ...model,
- titleMeshMaterialIndex,
- sceneOverride: applyTitleMeshMaterial(model.sceneOverride, preset),
- }),
- preset.name,
- context,
- );
-}
-
-function updateTitleEscapeKey(
- msg: KeyMsg,
- model: WorkspaceModel,
-): KeyBindingResult | undefined {
return msg.key === WorkspaceKeys.Escape
? [{ ...model, quitConfirmOpen: true }, []]
: undefined;
}
-
-function updateTitleCameraKey(
- msg: KeyMsg,
- model: WorkspaceModel,
-): KeyBindingResult | undefined {
- const update = updateTitleCameraFromKey(msg.key, model.titleCamera, {
- shift: msg.shift,
- });
- return update == null
- ? undefined
- : [
- activateLegacyTitleBackdrop({ ...model, titleCamera: update.state }),
- update.commands,
- ];
-}
-
-function activateLegacyTitleBackdrop(model: WorkspaceModel): WorkspaceModel {
- return {
- ...model,
- titleBackdropKind: TITLE_BACKDROP_KIND.LegacyScene,
- };
-}
-
-function pushTitleScreenToast(
- model: WorkspaceModel,
- message: string,
- context: WorkspaceKeyBindingContext,
-): KeyBindingResult {
- return pushToast(model, TITLE_SHADER_TOAST_TITLE, message, context);
-}
-
-function pushTitleMeshMaterialToast(
- model: WorkspaceModel,
- message: string,
- context: WorkspaceKeyBindingContext,
-): KeyBindingResult {
- return pushToast(model, TITLE_MESH_MATERIAL_TOAST_TITLE, message, context);
-}
-
-function pushAsciiPaletteToast(
- model: WorkspaceModel,
- palette: TitleAsciiPalette,
- context: WorkspaceKeyBindingContext,
-): KeyBindingResult {
- return pushToast(
- model,
- TITLE_ASCII_PALETTE_TOAST_TITLE,
- titleAsciiPaletteLabel(palette),
- context,
- );
-}
-
-function pushToast(
- model: WorkspaceModel,
- title: string,
- message: string,
- context: WorkspaceKeyBindingContext,
-): KeyBindingResult {
- return pushNotificationToast(
- model,
- {
- title,
- message,
- variant: NotificationVariants.Toast,
- tone: NotificationTones.Info,
- placement: NotificationPlacements.LowerRight,
- bgToken: titleToastBackgroundToken(model),
- accentToken: titleToastAccentToken(model),
- },
- context.nowMs(),
- context.createNotificationTickCmd,
- );
-}
-
-function asciiShaderLabel(model: WorkspaceModel): string {
- return `${TITLE_SHADER_ASCII_LABEL} · ${titleAsciiPaletteLabel(model.titleAsciiPalette)}`;
-}
-
-function isTitleMeshMaterialKey(msg: KeyMsg): boolean {
- return (
- !msg.ctrl &&
- !msg.alt &&
- (msg.key === WorkspaceKeys.M || msg.key === WorkspaceKeys.UpperM)
- );
-}
-
-function titleToastBackgroundToken(model: WorkspaceModel): TokenValue {
- return {
- hex:
- colorHex(model.jeditTheme.surface.workspace.fg) ??
- model.jeditTheme.surface.workspace.hex ??
- FALLBACK_TOAST_FOREGROUND,
- bg:
- colorHex(model.jeditTheme.surface.workspace.bg) ??
- FALLBACK_TOAST_BACKGROUND,
- };
-}
-
-function titleToastAccentToken(model: WorkspaceModel): TokenValue {
- return {
- hex:
- colorHex(model.jeditTheme.cursor.normal.bg) ??
- model.jeditTheme.cursor.normal.hex ??
- FALLBACK_TOAST_ACCENT,
- bg:
- colorHex(model.jeditTheme.surface.workspace.bg) ??
- FALLBACK_TOAST_BACKGROUND,
- };
-}
-
-function titleAsciiPaletteLabel(palette: TitleAsciiPalette): string {
- switch (palette) {
- case TITLE_ASCII_PALETTE.Dense:
- return TITLE_ASCII_PALETTE_DENSE_LABEL;
- case TITLE_ASCII_PALETTE.Minimal:
- return TITLE_ASCII_PALETTE_MINIMAL_LABEL;
- case TITLE_ASCII_PALETTE.Technical:
- return TITLE_ASCII_PALETTE_TECHNICAL_LABEL;
- case TITLE_ASCII_PALETTE.Hatching:
- return TITLE_ASCII_PALETTE_HATCHING_LABEL;
- case TITLE_ASCII_PALETTE.Matrix:
- return TITLE_ASCII_PALETTE_MATRIX_LABEL;
- case TITLE_ASCII_PALETTE.Blocks:
- return TITLE_ASCII_PALETTE_BLOCKS_LABEL;
- case TITLE_ASCII_PALETTE.Dither:
- return TITLE_ASCII_PALETTE_DITHER_LABEL;
- default: {
- const exhaustive: never = palette;
- throw new InvalidTitleAsciiPaletteError(String(exhaustive));
- }
- }
-}
diff --git a/src/app/workspace/viewer-content.ts b/src/app/workspace/viewer-content.ts
index 24054f30..a02d4450 100644
--- a/src/app/workspace/viewer-content.ts
+++ b/src/app/workspace/viewer-content.ts
@@ -1,150 +1,35 @@
-import { createSurface, type Surface } from "@flyingrobots/bijou";
-import { paintMarkdownPreview } from "../../ui/markdown-preview.js";
-import { renderSourceViewer } from "../../ui/source-viewer.js";
-import {
- TITLE_BACKDROP_KIND,
- TITLE_RENDER_MODE,
- paintTitleScreenPresentation,
- renderTitleScreen,
- type TitleScreenRenderOptions,
-} from "../../ui/title-screen.js";
-import type { JeditTheme } from "../../ui/jedit-theme.js";
-import {
- createBrailleSampleCache,
- createBrailleSampleFrameStats,
- type AveragingBrailleCanvasOptions,
- type BrailleSampleCache,
- type BrailleSampleFrameStats,
- type BrailleTraceBudget,
-} from "../../ui/averaging-braille-canvas.js";
-import type { WorkspaceModel } from "./model.js";
-import { isWorkspaceMarkdownFile } from "./editor-session.js";
-import { ViewModes } from "./view-mode.js";
-import { VIEWER_LEFT_PAD, VIEWER_TOP_PAD } from "./viewport.js";
+import { createSurface, type Surface } from '@flyingrobots/bijou';
+import { paintMarkdownPreview } from '../../ui/markdown-preview.js';
+import { renderSourceViewer } from '../../ui/source-viewer.js';
+import type { JeditTheme } from '../../ui/jedit-theme.js';
+import type { WorkspaceModel } from './model.js';
+import { isWorkspaceMarkdownFile } from './editor-session.js';
+import { ViewModes } from './view-mode.js';
+import { VIEWER_LEFT_PAD, VIEWER_TOP_PAD } from './viewport.js';
import {
causalSourceGutterDeletionMarkers,
sourceGutterLineMarkers,
displayEditorForWorkspaceModel,
sourceHighlightForWorkspaceProjection,
sourceWindowForWorkspaceModel,
-} from "./workspace-source-projection.js";
-import { fillSurface, renderJimLogoScreen } from "./surface-fill.js";
-import {
- governTitleSceneRender,
- staticTitleScenePerformanceFacts,
- titleScenePerformanceFacts,
- type TitleScenePerformanceFacts,
-} from "./title-scene-performance-governor.js";
-import {
- sameTitleBrailleSampleCacheIdentity,
- titleBrailleTraceBudget,
- titleBrailleSampleCacheIdentity,
- type TitleBrailleSampleCacheIdentity,
-} from "./title-braille-sampling.js";
+} from './workspace-source-projection.js';
+import { fillSurface, renderJimLogoScreen } from './surface-fill.js';
+
const MIN_VIEWPORT_DIMENSION = 1;
const VIEWER_PAD_MULTIPLIER = 2;
-const TITLE_CAMERA_MOTION_EPSILON = 0.001;
-const TITLE_FRAME_BUDGET_OVER = "over-budget";
-
-interface FrozenTitleBackdrop {
- readonly width: number;
- readonly height: number;
- readonly time: number;
- readonly identity: TitleBrailleSampleCacheIdentity;
- readonly camera: WorkspaceModel["titleCamera"];
- readonly asciiPalette: WorkspaceModel["titleAsciiPalette"];
- readonly lowRateActive: boolean;
- readonly surface: Surface;
-}
-
-interface ViewerContentRendererState {
- frozenTitleBackdrop?: FrozenTitleBackdrop;
- titlePresentationSurface?: Surface;
- lastTitleScenePerformance?: TitleScenePerformanceFacts;
- titleBrailleSampleCache?: BrailleSampleCache;
- titleBrailleSampleCacheIdentity?: TitleBrailleSampleCacheIdentity;
- titleBrailleFrameIndex?: number;
- lastBrailleSampleStats?: BrailleSampleFrameStats;
- lastBrailleTraceBudget?: BrailleTraceBudget;
-}
-
-interface TitleBrailleSamplingRenderContext {
- readonly options?: AveragingBrailleCanvasOptions;
- readonly stats?: BrailleSampleFrameStats;
- readonly budget?: BrailleTraceBudget;
-}
-
-export type TitleScreenRenderer = (
- width: number,
- height: number,
- time: number,
- theme: JeditTheme,
- options: TitleScreenRenderOptions,
-) => Surface;
export interface ViewerContentRenderer {
renderViewer(model: WorkspaceModel, width: number, height: number): Surface;
- clearFrozenTitleBackdrop(): void;
- titleScenePerformanceFacts(): TitleScenePerformanceFacts;
}
-export function createViewerContentRenderer(
- titleRenderer?: TitleScreenRenderer,
-): ViewerContentRenderer {
- const state: ViewerContentRendererState = {};
- return {
- renderViewer(model, width, height) {
- return renderViewerWithState(model, width, height, titleRenderer, state);
- },
- clearFrozenTitleBackdrop() {
- state.frozenTitleBackdrop = undefined;
- },
- titleScenePerformanceFacts() {
- return (
- state.lastTitleScenePerformance ?? staticTitleScenePerformanceFacts()
- );
- },
- };
-}
-
-export function renderViewer(
- model: WorkspaceModel,
- width: number,
- height: number,
-): Surface {
- return createViewerContentRenderer().renderViewer(model, width, height);
-}
-
-export function renderViewerWithTitleRenderer(
- model: WorkspaceModel,
- width: number,
- height: number,
- titleRenderer: TitleScreenRenderer,
-): Surface {
- return createViewerContentRenderer(titleRenderer).renderViewer(
- model,
- width,
- height,
- );
+export function createViewerContentRenderer(): ViewerContentRenderer {
+ return { renderViewer };
}
-function renderViewerWithState(
- model: WorkspaceModel,
- width: number,
- height: number,
- titleRenderer: TitleScreenRenderer | undefined,
- state: ViewerContentRendererState,
-): Surface {
+export function renderViewer(model: WorkspaceModel, width: number, height: number): Surface {
const editor = displayEditorForWorkspaceModel(model);
if (editor == null) {
- // jedit opens the way vi does: on nothing. The ray-traced backdrop is
- // still reachable -- the scene picker and the title number keys set
- // LegacyScene, and callers may inject a renderer directly -- but no
- // launch selects it, so startup renders an empty viewer.
- return model.titleBackdropKind === TITLE_BACKDROP_KIND.LegacyScene ||
- titleRenderer != null
- ? renderTitleViewer(model, width, height, titleRenderer, state)
- : emptyViewerSurface(model, width, height);
+ return renderJimLogoScreen(width, height, model.jeditTheme);
}
const surface = createSurface(width, height);
@@ -176,291 +61,6 @@ function renderViewerWithState(
);
}
-// vi opens on nothing; jedit opens on its own mark. This is a single static
-// render of committed artwork -- no scene, no meshes, and no frame pulse, so it
-// costs one draw rather than an animation.
-function emptyViewerSurface(
- model: WorkspaceModel,
- width: number,
- height: number,
-): Surface {
- return renderJimLogoScreen(width, height, model.jeditTheme);
-}
-
-function renderTitleViewer(
- model: WorkspaceModel,
- width: number,
- height: number,
- injectedRenderer: TitleScreenRenderer | undefined,
- state: ViewerContentRendererState,
-): Surface {
- const renderer =
- injectedRenderer ??
- (model.titleBackdropKind === TITLE_BACKDROP_KIND.LegacyScene
- ? renderTitleScreen
- : undefined);
- return renderer == null
- ? renderDefaultTitleFrame(model, width, height, state)
- : renderTitleBackdrop(model, width, height, renderer, state);
-}
-
-function renderDefaultTitleFrame(
- model: WorkspaceModel,
- width: number,
- height: number,
- state: ViewerContentRendererState,
-): Surface {
- // No backdrop is the default. The legacy ray-traced scene is the only
- // title presentation jedit still draws, and only when a caller asks.
- state.lastTitleScenePerformance = staticTitleScenePerformanceFacts();
- return titleFrameSurface(
- emptyViewerSurface(model, width, height),
- model,
- width,
- height,
- state,
- );
-}
-
-function renderTitleBackdrop(
- model: WorkspaceModel,
- width: number,
- height: number,
- titleRenderer: TitleScreenRenderer,
- state: ViewerContentRendererState,
-): Surface {
- const frozen = frozenTitleBackdropFor(state, model, width, height);
- const decision = governTitleSceneRender(
- titleSceneGovernorInput(model, frozen),
- );
- state.lastTitleScenePerformance = titleScenePerformanceFacts(decision);
- if (decision.shouldUseFrozenBackdrop && frozen != null) {
- state.frozenTitleBackdrop = activateLowRateBackdrop(frozen, decision);
- return titleFrameSurface(frozen.surface, model, width, height, state);
- }
-
- const rendered = renderLiveTitleBackdrop(
- model,
- width,
- height,
- titleRenderer,
- state,
- );
- if (decision.shouldRetainRenderedBackdrop) {
- state.frozenTitleBackdrop = {
- width,
- height,
- time: model.time,
- identity: titleBrailleSampleCacheIdentity(model, width, height),
- camera: model.titleCamera,
- asciiPalette: model.titleAsciiPalette,
- lowRateActive: shouldActivateLowRateBackdrop(model, decision),
- surface: rendered,
- };
- }
- return titleFrameSurface(rendered, model, width, height, state);
-}
-
-function titleSceneGovernorInput(
- model: WorkspaceModel,
- frozen: FrozenTitleBackdrop | undefined,
-) {
- return {
- introActive: model.editor == null && !model.startupIntroComplete,
- idleTitleScreen:
- model.editor == null &&
- model.startupIntroComplete &&
- !model.startupFileModalOpen,
- frozenBackdropAvailable: frozen != null,
- lowRateFrozenBackdropActive: frozen?.lowRateActive,
- frameTimeMs: model.frameTimeMs ?? 0,
- };
-}
-
-function renderLiveTitleBackdrop(
- model: WorkspaceModel,
- width: number,
- height: number,
- titleRenderer: TitleScreenRenderer,
- state: ViewerContentRendererState,
-): Surface {
- const sampling = titleBrailleSamplingRenderContext(
- model,
- width,
- height,
- state,
- );
- const rendered = titleRenderer(width, height, model.time, model.jeditTheme, {
- camAngle: model.titleCamera.angle,
- camRadius: model.titleCamera.radius,
- camera: model.titleCamera,
- sceneSeed: model.titleSceneSeed,
- wallClockMs: model.lastFrameMs,
- mesh: model.titleMeshes.bunny,
- sceneOverride: model.sceneOverride,
- renderMode: model.titleRenderMode,
- asciiPalette: model.titleAsciiPalette,
- textDirection: model.i18n.direction,
- brailleSampling: sampling.options,
- suppressPresentation: true,
- });
- recordTitleBrailleSamplingStats(state, sampling);
- return rendered;
-}
-
-function titleFrameSurface(
- backdrop: Surface,
- model: WorkspaceModel,
- width: number,
- height: number,
- state: ViewerContentRendererState,
-): Surface {
- if (model.editor != null || model.startupIntroComplete) {
- return backdrop;
- }
- const surface =
- state.titlePresentationSurface?.width === width &&
- state.titlePresentationSurface.height === height
- ? state.titlePresentationSurface
- : createSurface(width, height);
- state.titlePresentationSurface = surface;
- surface.blit(backdrop, 0, 0);
- paintTitleScreenPresentation(surface, {
- cols: width,
- rows: height,
- time: model.time,
- theme: model.jeditTheme,
- textDirection: model.i18n.direction,
- });
- return surface;
-}
-
-function titleBrailleSamplingRenderContext(
- model: WorkspaceModel,
- width: number,
- height: number,
- state: ViewerContentRendererState,
-): TitleBrailleSamplingRenderContext {
- if (model.titleRenderMode !== TITLE_RENDER_MODE.Braille) {
- clearTitleBrailleSamplingState(state);
- return {};
- }
- const stats = createBrailleSampleFrameStats();
- const cache = titleBrailleSampleCacheFor(model, width, height, state);
- const frameIndex = state.titleBrailleFrameIndex ?? 0;
- const budget = titleBrailleTraceBudget({
- frameIndex,
- frameTimeMs: model.frameTimeMs,
- previousStats: state.lastBrailleSampleStats,
- previousPhaseCount: state.lastBrailleTraceBudget?.phaseCount,
- cameraMoving: titleCameraIsMoving(model.titleCamera),
- });
- state.titleBrailleFrameIndex = frameIndex + 1;
- return {
- stats,
- budget,
- options: {
- sampleCache: cache,
- stats,
- traceBudget: budget,
- },
- };
-}
-
-function titleBrailleSampleCacheFor(
- model: WorkspaceModel,
- width: number,
- height: number,
- state: ViewerContentRendererState,
-): BrailleSampleCache {
- const identity = titleBrailleSampleCacheIdentity(model, width, height);
- if (
- state.titleBrailleSampleCache == null ||
- state.titleBrailleSampleCacheIdentity == null ||
- !sameTitleBrailleSampleCacheIdentity(
- state.titleBrailleSampleCacheIdentity,
- identity,
- )
- ) {
- state.titleBrailleSampleCache = createBrailleSampleCache(width, height);
- state.titleBrailleSampleCacheIdentity = identity;
- clearTitleBrailleSamplingState(state);
- }
- return state.titleBrailleSampleCache;
-}
-
-function clearTitleBrailleSamplingState(
- state: ViewerContentRendererState,
-): void {
- state.titleBrailleFrameIndex = 0;
- state.lastBrailleSampleStats = undefined;
- state.lastBrailleTraceBudget = undefined;
-}
-
-function titleCameraIsMoving(camera: WorkspaceModel["titleCamera"]): boolean {
- return (
- titleCameraAxisIsMoving(camera.angle, camera.angleTarget) ||
- titleCameraAxisIsMoving(camera.radius, camera.radiusTarget)
- );
-}
-
-function titleCameraAxisIsMoving(current: number, target: number): boolean {
- return Math.abs(current - target) > TITLE_CAMERA_MOTION_EPSILON;
-}
-
-function recordTitleBrailleSamplingStats(
- state: ViewerContentRendererState,
- context: TitleBrailleSamplingRenderContext,
-): void {
- if (context.stats != null && context.stats.totalSamples > 0) {
- state.lastBrailleSampleStats = context.stats;
- }
- if (context.budget != null) {
- state.lastBrailleTraceBudget = context.budget;
- }
-}
-
-function frozenTitleBackdropFor(
- state: ViewerContentRendererState,
- model: WorkspaceModel,
- width: number,
- height: number,
-): FrozenTitleBackdrop | undefined {
- return state.frozenTitleBackdrop?.width === width &&
- state.frozenTitleBackdrop.height === height &&
- state.frozenTitleBackdrop.camera === model.titleCamera &&
- state.frozenTitleBackdrop.asciiPalette === model.titleAsciiPalette &&
- sameTitleBrailleSampleCacheIdentity(
- state.frozenTitleBackdrop.identity,
- titleBrailleSampleCacheIdentity(model, width, height),
- )
- ? state.frozenTitleBackdrop
- : undefined;
-}
-
-function activateLowRateBackdrop(
- frozen: FrozenTitleBackdrop,
- decision: FrameBudgetDecision,
-): FrozenTitleBackdrop {
- return decision.frameBudgetPosture === TITLE_FRAME_BUDGET_OVER
- ? { ...frozen, lowRateActive: true }
- : frozen;
-}
-
-function shouldActivateLowRateBackdrop(
- model: WorkspaceModel,
- decision: FrameBudgetDecision,
-): boolean {
- return (
- model.editor == null &&
- !model.startupFileModalOpen &&
- decision.frameBudgetPosture === TITLE_FRAME_BUDGET_OVER
- );
-}
-
-interface FrameBudgetDecision {
- readonly frameBudgetPosture: TitleScenePerformanceFacts["frameBudgetPosture"];
-}
export function isWorkspaceMarkdownPreviewAvailable(
model: WorkspaceModel,
): boolean {
diff --git a/src/app/workspace/viewer-overlays.ts b/src/app/workspace/viewer-overlays.ts
index 8ecb424f..5aa11dda 100644
--- a/src/app/workspace/viewer-overlays.ts
+++ b/src/app/workspace/viewer-overlays.ts
@@ -4,7 +4,6 @@ import {
renderGraftDiagnosticsPanel,
resolveGraftDiagnosticsPanelWidth,
} from '../../ui/graft-diagnostics-panel.js';
-import { resolveScenePickerDrawerWidth, renderScenePickerDrawer } from '../../ui/scene-picker-drawer.js';
import { resolveSettingsDrawerWidth, renderSettingsDrawer } from '../../ui/settings-drawer.js';
import { renderStartupFileDrawer } from '../../ui/startup-file-modal.js';
import type { WorkspaceModel } from './model.js';
@@ -31,20 +30,6 @@ export function paintWorkspaceOverlays(
): void {
paintSettingsOverlay(screen, model, bodyTop, bodyHeight);
- if (model.scenePickerOpen && model.editor == null) {
- screen.blit(
- renderScenePickerDrawer({
- scenes: model.availableScenes,
- selectedIndex: model.scenePickerFocusIndex,
- theme: model.jeditTheme,
- width: resolveScenePickerDrawerWidth(model.columns),
- height: bodyHeight,
- }),
- 0,
- bodyTop,
- );
- }
-
paintStartupFileDrawer(screen, model, bodyTop, bodyHeight);
paintWorkspaceInlinePanelOverlay(screen, model, bodyTop, bodyHeight);
paintWorkspaceCommandCompletionOverlay(screen, model);
diff --git a/src/app/workspace/workspace-inline-panel.ts b/src/app/workspace/workspace-inline-panel.ts
index 66482e70..005eb497 100644
--- a/src/app/workspace/workspace-inline-panel.ts
+++ b/src/app/workspace/workspace-inline-panel.ts
@@ -200,7 +200,6 @@ function sourceEditorOwnsInlinePanel(model: WorkspaceModel): boolean {
model.viewMode === ViewModes.Source &&
model.quitConfirmOpen !== true &&
model.settingsOpen !== true &&
- model.scenePickerOpen !== true &&
model.startupFileModalOpen !== true &&
model.commandLine.active !== true;
}
diff --git a/src/app/workspace/workspace-runtime-dependencies.ts b/src/app/workspace/workspace-runtime-dependencies.ts
index 7ce31018..13a62c31 100644
--- a/src/app/workspace/workspace-runtime-dependencies.ts
+++ b/src/app/workspace/workspace-runtime-dependencies.ts
@@ -5,7 +5,6 @@ import type { FileSystemPort } from '../../ports/file-system.js';
import type { GraftDiagnosticsPort } from '../../ports/graft-diagnostics.js';
import type { GraftSessionPort } from '../../ports/graft-session.js';
import type { SourceHighlighter } from '../../ports/source-highlighter.js';
-import type { TitleSceneLoaderPort } from '../../ports/title-scene-loader.js';
import type { WorkspaceInitialModelSnapshot } from './init.js';
import type { CreateStartupFileDrawerAnimationCmd } from './key-binding-context.js';
import type { WorkspaceModel } from './model.js';
@@ -32,7 +31,6 @@ export interface WorkspaceRuntimeDependencies {
readonly graftDiagnostics: GraftDiagnosticsPort;
readonly graftSession: GraftSessionPort;
readonly sourceHighlighter: SourceHighlighter;
- readonly titleSceneLoader: TitleSceneLoaderPort;
readonly productionTextSession: ProductionTextSession;
readonly profiler: ProfilerTracePort;
readonly profileOnStartup: boolean;
diff --git a/src/app/workspace/workspace-state-reducers.ts b/src/app/workspace/workspace-state-reducers.ts
index b5b66220..034c463e 100644
--- a/src/app/workspace/workspace-state-reducers.ts
+++ b/src/app/workspace/workspace-state-reducers.ts
@@ -9,4 +9,3 @@ export { applyWorkspaceTextMessage } from './workspace-text-message-reducer.js';
export { applyWorkspaceCausalLineChangeResult } from './workspace-causal-line-change-refresh.js';
export { syncActiveWorkspaceBufferRecord } from './workspace-buffer-registry.js';
export { applyWorkspaceWhyRangeResult } from './workspace-why-range.js';
-export { applyWorkspaceTitleSceneLoadResult } from './workspace-title-scene-state.js';
diff --git a/src/app/workspace/workspace-title-scene-state.ts b/src/app/workspace/workspace-title-scene-state.ts
deleted file mode 100644
index d14f1dbf..00000000
--- a/src/app/workspace/workspace-title-scene-state.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { createTitleCameraState } from '../title-camera-session.js';
-import { TITLE_BACKDROP_KIND } from '../../ui/title-screen.js';
-import type { WorkspaceModel } from './model.js';
-import { WorkspaceMessageTypes, type WorkspaceMsg } from './msg.js';
-
-export function applyWorkspaceTitleSceneLoadResult(
- model: WorkspaceModel,
- msg: Extract<
- WorkspaceMsg,
- { type: typeof WorkspaceMessageTypes.LoadSceneResult }
- >,
-): WorkspaceModel {
- // A result carrying no scene is a cancellation or a RuntimeIssue, not a
- // backdrop change. Switching to LegacyScene regardless dropped the static
- // logo for a scene that was never loaded, and because
- // workspaceAnimationIsActive counts LegacyScene as animating, it also put the
- // workspace back into a 60Hz render loop -- the #320 regression, on the
- // failure path.
- if (msg.scene == null) {
- return model;
- }
- return {
- ...model,
- sceneOverride: msg.scene,
- titleSceneName: msg.sceneName,
- titleBackdropKind: TITLE_BACKDROP_KIND.LegacyScene,
- titleCamera: createTitleCameraState(msg.scene.camera),
- };
-}
diff --git a/src/generated/i18n.ts b/src/generated/i18n.ts
index 6981417b..5cbbfc1b 100644
--- a/src/generated/i18n.ts
+++ b/src/generated/i18n.ts
@@ -49,8 +49,6 @@ export type TranslationSchema = {
readonly "ctrl_t_theme": string;
readonly "tab_focus": string;
readonly "tab_indent": string;
- readonly "ctrl_l_scene_picker": string;
- readonly "scene_picker": string;
readonly "ctrl_b_files": string;
readonly "ctrl_g_graft": string;
readonly "ctrl_h_history": string;
@@ -178,47 +176,47 @@ export interface LocaleMetadata {
readonly direction: LocaleDirection;
}
-export const en: TranslationSchema = {"footer":{"mode":{"browse":"browse","insert":"insert","normal":"normal","preview":"preview","settings":"settings","files":"files","graft":"graft","history":"history"},"context":{"settings":"settings","graft_empty":"open a file to inspect it","history_empty":"no Echo evidence yet","history_count":"Echo evidence: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k move","enter_change":"enter change","f2_close":"f2 close","esc_close":"esc close","j_k_scroll":"j/k scroll","f3_source":"f3 source","f3_preview":"f3 preview","text_input":"text input","esc_normal":"esc normal","ctrl_s_save":"ctrl+s save","ctrl_t_theme":"ctrl+t theme","tab_focus":"tab focus","tab_indent":"tab indent","ctrl_l_scene_picker":"ctrl+l scene picker","scene_picker":"scene picker","ctrl_b_files":"ctrl+b files","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h history","enter_open":"enter open","backspace_up":"backspace up","ctrl_b_close":"ctrl+b close","enter_jump":"enter jump","r_refresh":"r refresh","ctrl_g_close":"ctrl+g close","ctrl_h_close":"ctrl+h close","i_insert":"i insert","o_open_line":"o open line","cc_line":"cc line","cw_word":"cw word","ce_word_end":"ce word-end","c0_start":"c0 start","c_end":"c$ end","dd_line":"dd line","dw_word":"dw word","de_word_end":"de word-end","d0_start":"d0 start","d_end":"d$ end","yy_line":"yy line","yw_word":"yw word","ye_word_end":"ye word-end","y0_start":"y0 start","y_end":"y$ end","gg_top":"gg top","esc_cancel":"esc cancel"},"evidence":{"head":"head","tick":"tick","anchor":"anchor","coverage":"coverage","complete":"complete","partial":"partial","unavailable":"unavailable"}},"history":{"title":"Echo History","empty":"No Echo evidence yet","header":"# tick kind status evidence summary"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const en: TranslationSchema = {"footer":{"mode":{"browse":"browse","insert":"insert","normal":"normal","preview":"preview","settings":"settings","files":"files","graft":"graft","history":"history"},"context":{"settings":"settings","graft_empty":"open a file to inspect it","history_empty":"no Echo evidence yet","history_count":"Echo evidence: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k move","enter_change":"enter change","f2_close":"f2 close","esc_close":"esc close","j_k_scroll":"j/k scroll","f3_source":"f3 source","f3_preview":"f3 preview","text_input":"text input","esc_normal":"esc normal","ctrl_s_save":"ctrl+s save","ctrl_t_theme":"ctrl+t theme","tab_focus":"tab focus","tab_indent":"tab indent","ctrl_b_files":"ctrl+b files","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h history","enter_open":"enter open","backspace_up":"backspace up","ctrl_b_close":"ctrl+b close","enter_jump":"enter jump","r_refresh":"r refresh","ctrl_g_close":"ctrl+g close","ctrl_h_close":"ctrl+h close","i_insert":"i insert","o_open_line":"o open line","cc_line":"cc line","cw_word":"cw word","ce_word_end":"ce word-end","c0_start":"c0 start","c_end":"c$ end","dd_line":"dd line","dw_word":"dw word","de_word_end":"de word-end","d0_start":"d0 start","d_end":"d$ end","yy_line":"yy line","yw_word":"yw word","ye_word_end":"ye word-end","y0_start":"y0 start","y_end":"y$ end","gg_top":"gg top","esc_cancel":"esc cancel"},"evidence":{"head":"head","tick":"tick","anchor":"anchor","coverage":"coverage","complete":"complete","partial":"partial","unavailable":"unavailable"}},"history":{"title":"Echo History","empty":"No Echo evidence yet","header":"# tick kind status evidence summary"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const fr: TranslationSchema = {"footer":{"mode":{"browse":"parcourir","insert":"insertion","normal":"normal","preview":"aperçu","settings":"paramètres","files":"fichiers","graft":"graft","history":"historique"},"context":{"settings":"paramètres","graft_empty":"ouvrez un fichier pour l'inspecter","history_empty":"pas encore de preuve Echo","history_count":"Preuves Echo : {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k déplacer","enter_change":"enter changer","f2_close":"f2 fermer","esc_close":"esc fermer","j_k_scroll":"j/k défiler","f3_source":"f3 source","f3_preview":"f3 aperçu","text_input":"saisie texte","esc_normal":"esc normal","ctrl_s_save":"ctrl+s enregistrer","ctrl_t_theme":"ctrl+t thème","tab_focus":"tab focus","tab_indent":"tab retrait","ctrl_l_scene_picker":"ctrl+l scène","scene_picker":"sélecteur de scène","ctrl_b_files":"ctrl+b fichiers","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h historique","enter_open":"enter ouvrir","backspace_up":"backspace haut","ctrl_b_close":"ctrl+b fermer","enter_jump":"enter aller","r_refresh":"r actualiser","ctrl_g_close":"ctrl+g fermer","ctrl_h_close":"ctrl+h fermer","i_insert":"i insérer","o_open_line":"o ouvrir ligne","cc_line":"cc ligne","cw_word":"cw mot","ce_word_end":"ce fin mot","c0_start":"c0 début","c_end":"c$ fin","dd_line":"dd ligne","dw_word":"dw mot","de_word_end":"de fin mot","d0_start":"d0 début","d_end":"d$ fin","yy_line":"yy ligne","yw_word":"yw mot","ye_word_end":"ye fin mot","y0_start":"y0 début","y_end":"y$ fin","gg_top":"gg haut","esc_cancel":"esc annuler"},"evidence":{"head":"tête","tick":"tick","anchor":"ancre","coverage":"couverture","complete":"complète","partial":"partielle","unavailable":"indisponible"}},"history":{"title":"Historique Echo","empty":"Pas encore de preuve Echo","header":"# tick type statut preuve résumé"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const fr: TranslationSchema = {"footer":{"mode":{"browse":"parcourir","insert":"insertion","normal":"normal","preview":"aperçu","settings":"paramètres","files":"fichiers","graft":"graft","history":"historique"},"context":{"settings":"paramètres","graft_empty":"ouvrez un fichier pour l'inspecter","history_empty":"pas encore de preuve Echo","history_count":"Preuves Echo : {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k déplacer","enter_change":"enter changer","f2_close":"f2 fermer","esc_close":"esc fermer","j_k_scroll":"j/k défiler","f3_source":"f3 source","f3_preview":"f3 aperçu","text_input":"saisie texte","esc_normal":"esc normal","ctrl_s_save":"ctrl+s enregistrer","ctrl_t_theme":"ctrl+t thème","tab_focus":"tab focus","tab_indent":"tab retrait","ctrl_b_files":"ctrl+b fichiers","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h historique","enter_open":"enter ouvrir","backspace_up":"backspace haut","ctrl_b_close":"ctrl+b fermer","enter_jump":"enter aller","r_refresh":"r actualiser","ctrl_g_close":"ctrl+g fermer","ctrl_h_close":"ctrl+h fermer","i_insert":"i insérer","o_open_line":"o ouvrir ligne","cc_line":"cc ligne","cw_word":"cw mot","ce_word_end":"ce fin mot","c0_start":"c0 début","c_end":"c$ fin","dd_line":"dd ligne","dw_word":"dw mot","de_word_end":"de fin mot","d0_start":"d0 début","d_end":"d$ fin","yy_line":"yy ligne","yw_word":"yw mot","ye_word_end":"ye fin mot","y0_start":"y0 début","y_end":"y$ fin","gg_top":"gg haut","esc_cancel":"esc annuler"},"evidence":{"head":"tête","tick":"tick","anchor":"ancre","coverage":"couverture","complete":"complète","partial":"partielle","unavailable":"indisponible"}},"history":{"title":"Historique Echo","empty":"Pas encore de preuve Echo","header":"# tick type statut preuve résumé"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const it: TranslationSchema = {"footer":{"mode":{"browse":"sfoglia","insert":"inserisci","normal":"normale","preview":"anteprima","settings":"impostazioni","files":"file","graft":"graft","history":"cronologia"},"context":{"settings":"impostazioni","graft_empty":"apri un file per ispezionarlo","history_empty":"nessuna prova Echo ancora","history_count":"Prove Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k muovi","enter_change":"enter cambia","f2_close":"f2 chiudi","esc_close":"esc chiudi","j_k_scroll":"j/k scorri","f3_source":"f3 sorgente","f3_preview":"f3 anteprima","text_input":"testo","esc_normal":"esc normale","ctrl_s_save":"ctrl+s salva","ctrl_t_theme":"ctrl+t tema","tab_focus":"tab focus","tab_indent":"tab rientra","ctrl_l_scene_picker":"ctrl+l scena","scene_picker":"selettore scena","ctrl_b_files":"ctrl+b file","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h cronologia","enter_open":"enter apri","backspace_up":"backspace su","ctrl_b_close":"ctrl+b chiudi","enter_jump":"enter vai","r_refresh":"r aggiorna","ctrl_g_close":"ctrl+g chiudi","ctrl_h_close":"ctrl+h chiudi","i_insert":"i inserisci","o_open_line":"o apri riga","cc_line":"cc riga","cw_word":"cw parola","ce_word_end":"ce fine parola","c0_start":"c0 inizio","c_end":"c$ fine","dd_line":"dd riga","dw_word":"dw parola","de_word_end":"de fine parola","d0_start":"d0 inizio","d_end":"d$ fine","yy_line":"yy riga","yw_word":"yw parola","ye_word_end":"ye fine parola","y0_start":"y0 inizio","y_end":"y$ fine","gg_top":"gg cima","esc_cancel":"esc annulla"},"evidence":{"head":"testa","tick":"tick","anchor":"ancora","coverage":"copertura","complete":"completa","partial":"parziale","unavailable":"non disponibile"}},"history":{"title":"Cronologia Echo","empty":"Nessuna prova Echo ancora","header":"# tick tipo stato prova riepilogo"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const it: TranslationSchema = {"footer":{"mode":{"browse":"sfoglia","insert":"inserisci","normal":"normale","preview":"anteprima","settings":"impostazioni","files":"file","graft":"graft","history":"cronologia"},"context":{"settings":"impostazioni","graft_empty":"apri un file per ispezionarlo","history_empty":"nessuna prova Echo ancora","history_count":"Prove Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k muovi","enter_change":"enter cambia","f2_close":"f2 chiudi","esc_close":"esc chiudi","j_k_scroll":"j/k scorri","f3_source":"f3 sorgente","f3_preview":"f3 anteprima","text_input":"testo","esc_normal":"esc normale","ctrl_s_save":"ctrl+s salva","ctrl_t_theme":"ctrl+t tema","tab_focus":"tab focus","tab_indent":"tab rientra","ctrl_b_files":"ctrl+b file","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h cronologia","enter_open":"enter apri","backspace_up":"backspace su","ctrl_b_close":"ctrl+b chiudi","enter_jump":"enter vai","r_refresh":"r aggiorna","ctrl_g_close":"ctrl+g chiudi","ctrl_h_close":"ctrl+h chiudi","i_insert":"i inserisci","o_open_line":"o apri riga","cc_line":"cc riga","cw_word":"cw parola","ce_word_end":"ce fine parola","c0_start":"c0 inizio","c_end":"c$ fine","dd_line":"dd riga","dw_word":"dw parola","de_word_end":"de fine parola","d0_start":"d0 inizio","d_end":"d$ fine","yy_line":"yy riga","yw_word":"yw parola","ye_word_end":"ye fine parola","y0_start":"y0 inizio","y_end":"y$ fine","gg_top":"gg cima","esc_cancel":"esc annulla"},"evidence":{"head":"testa","tick":"tick","anchor":"ancora","coverage":"copertura","complete":"completa","partial":"parziale","unavailable":"non disponibile"}},"history":{"title":"Cronologia Echo","empty":"Nessuna prova Echo ancora","header":"# tick tipo stato prova riepilogo"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const de: TranslationSchema = {"footer":{"mode":{"browse":"durchsuchen","insert":"einfügen","normal":"normal","preview":"vorschau","settings":"einstellungen","files":"dateien","graft":"graft","history":"verlauf"},"context":{"settings":"einstellungen","graft_empty":"datei öffnen zum prüfen","history_empty":"noch keine Echo-Nachweise","history_count":"Echo-Nachweise: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k bewegen","enter_change":"enter ändern","f2_close":"f2 schließen","esc_close":"esc schließen","j_k_scroll":"j/k scrollen","f3_source":"f3 quelltext","f3_preview":"f3 vorschau","text_input":"texteingabe","esc_normal":"esc normal","ctrl_s_save":"ctrl+s speichern","ctrl_t_theme":"ctrl+t theme","tab_focus":"tab fokus","tab_indent":"tab einrücken","ctrl_l_scene_picker":"ctrl+l szene","scene_picker":"szenenauswahl","ctrl_b_files":"ctrl+b dateien","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h verlauf","enter_open":"enter öffnen","backspace_up":"backspace hoch","ctrl_b_close":"ctrl+b schließen","enter_jump":"enter springen","r_refresh":"r aktualisieren","ctrl_g_close":"ctrl+g schließen","ctrl_h_close":"ctrl+h schließen","i_insert":"i einfügen","o_open_line":"o zeile öffnen","cc_line":"cc zeile","cw_word":"cw wort","ce_word_end":"ce wortende","c0_start":"c0 anfang","c_end":"c$ ende","dd_line":"dd zeile","dw_word":"dw wort","de_word_end":"de wortende","d0_start":"d0 anfang","d_end":"d$ ende","yy_line":"yy zeile","yw_word":"yw wort","ye_word_end":"ye wortende","y0_start":"y0 anfang","y_end":"y$ ende","gg_top":"gg oben","esc_cancel":"esc abbrechen"},"evidence":{"head":"Kopf","tick":"Tick","anchor":"Anker","coverage":"Abdeckung","complete":"vollständig","partial":"teilweise","unavailable":"nicht verfügbar"}},"history":{"title":"Echo-Verlauf","empty":"Noch keine Echo-Nachweise","header":"# tick art status nachweis zusammenfassung"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const de: TranslationSchema = {"footer":{"mode":{"browse":"durchsuchen","insert":"einfügen","normal":"normal","preview":"vorschau","settings":"einstellungen","files":"dateien","graft":"graft","history":"verlauf"},"context":{"settings":"einstellungen","graft_empty":"datei öffnen zum prüfen","history_empty":"noch keine Echo-Nachweise","history_count":"Echo-Nachweise: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k bewegen","enter_change":"enter ändern","f2_close":"f2 schließen","esc_close":"esc schließen","j_k_scroll":"j/k scrollen","f3_source":"f3 quelltext","f3_preview":"f3 vorschau","text_input":"texteingabe","esc_normal":"esc normal","ctrl_s_save":"ctrl+s speichern","ctrl_t_theme":"ctrl+t theme","tab_focus":"tab fokus","tab_indent":"tab einrücken","ctrl_b_files":"ctrl+b dateien","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h verlauf","enter_open":"enter öffnen","backspace_up":"backspace hoch","ctrl_b_close":"ctrl+b schließen","enter_jump":"enter springen","r_refresh":"r aktualisieren","ctrl_g_close":"ctrl+g schließen","ctrl_h_close":"ctrl+h schließen","i_insert":"i einfügen","o_open_line":"o zeile öffnen","cc_line":"cc zeile","cw_word":"cw wort","ce_word_end":"ce wortende","c0_start":"c0 anfang","c_end":"c$ ende","dd_line":"dd zeile","dw_word":"dw wort","de_word_end":"de wortende","d0_start":"d0 anfang","d_end":"d$ ende","yy_line":"yy zeile","yw_word":"yw wort","ye_word_end":"ye wortende","y0_start":"y0 anfang","y_end":"y$ ende","gg_top":"gg oben","esc_cancel":"esc abbrechen"},"evidence":{"head":"Kopf","tick":"Tick","anchor":"Anker","coverage":"Abdeckung","complete":"vollständig","partial":"teilweise","unavailable":"nicht verfügbar"}},"history":{"title":"Echo-Verlauf","empty":"Noch keine Echo-Nachweise","header":"# tick art status nachweis zusammenfassung"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const es: TranslationSchema = {"footer":{"mode":{"browse":"explorar","insert":"insertar","normal":"normal","preview":"vista previa","settings":"ajustes","files":"archivos","graft":"graft","history":"historial"},"context":{"settings":"ajustes","graft_empty":"abre un archivo para inspeccionarlo","history_empty":"sin evidencia Echo todavía","history_count":"Evidencia Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k mover","enter_change":"enter cambiar","f2_close":"f2 cerrar","esc_close":"esc cerrar","j_k_scroll":"j/k desplazar","f3_source":"f3 fuente","f3_preview":"f3 vista previa","text_input":"texto","esc_normal":"esc normal","ctrl_s_save":"ctrl+s guardar","ctrl_t_theme":"ctrl+t tema","tab_focus":"tab foco","tab_indent":"tab sangría","ctrl_l_scene_picker":"ctrl+l escena","scene_picker":"selector de escena","ctrl_b_files":"ctrl+b archivos","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h historial","enter_open":"enter abrir","backspace_up":"backspace arriba","ctrl_b_close":"ctrl+b cerrar","enter_jump":"enter saltar","r_refresh":"r actualizar","ctrl_g_close":"ctrl+g cerrar","ctrl_h_close":"ctrl+h cerrar","i_insert":"i insertar","o_open_line":"o abrir línea","cc_line":"cc línea","cw_word":"cw palabra","ce_word_end":"ce fin palabra","c0_start":"c0 inicio","c_end":"c$ fin","dd_line":"dd línea","dw_word":"dw palabra","de_word_end":"de fin palabra","d0_start":"d0 inicio","d_end":"d$ fin","yy_line":"yy línea","yw_word":"yw palabra","ye_word_end":"ye fin palabra","y0_start":"y0 inicio","y_end":"y$ fin","gg_top":"gg arriba","esc_cancel":"esc cancelar"},"evidence":{"head":"cabeza","tick":"tick","anchor":"ancla","coverage":"cobertura","complete":"completa","partial":"parcial","unavailable":"no disponible"}},"history":{"title":"Historial Echo","empty":"Sin evidencia Echo todavía","header":"# tick tipo estado evidencia resumen"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const es: TranslationSchema = {"footer":{"mode":{"browse":"explorar","insert":"insertar","normal":"normal","preview":"vista previa","settings":"ajustes","files":"archivos","graft":"graft","history":"historial"},"context":{"settings":"ajustes","graft_empty":"abre un archivo para inspeccionarlo","history_empty":"sin evidencia Echo todavía","history_count":"Evidencia Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k mover","enter_change":"enter cambiar","f2_close":"f2 cerrar","esc_close":"esc cerrar","j_k_scroll":"j/k desplazar","f3_source":"f3 fuente","f3_preview":"f3 vista previa","text_input":"texto","esc_normal":"esc normal","ctrl_s_save":"ctrl+s guardar","ctrl_t_theme":"ctrl+t tema","tab_focus":"tab foco","tab_indent":"tab sangría","ctrl_b_files":"ctrl+b archivos","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h historial","enter_open":"enter abrir","backspace_up":"backspace arriba","ctrl_b_close":"ctrl+b cerrar","enter_jump":"enter saltar","r_refresh":"r actualizar","ctrl_g_close":"ctrl+g cerrar","ctrl_h_close":"ctrl+h cerrar","i_insert":"i insertar","o_open_line":"o abrir línea","cc_line":"cc línea","cw_word":"cw palabra","ce_word_end":"ce fin palabra","c0_start":"c0 inicio","c_end":"c$ fin","dd_line":"dd línea","dw_word":"dw palabra","de_word_end":"de fin palabra","d0_start":"d0 inicio","d_end":"d$ fin","yy_line":"yy línea","yw_word":"yw palabra","ye_word_end":"ye fin palabra","y0_start":"y0 inicio","y_end":"y$ fin","gg_top":"gg arriba","esc_cancel":"esc cancelar"},"evidence":{"head":"cabeza","tick":"tick","anchor":"ancla","coverage":"cobertura","complete":"completa","partial":"parcial","unavailable":"no disponible"}},"history":{"title":"Historial Echo","empty":"Sin evidencia Echo todavía","header":"# tick tipo estado evidencia resumen"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const ko: TranslationSchema = {"footer":{"mode":{"browse":"탐색","insert":"삽입","normal":"일반","preview":"미리보기","settings":"설정","files":"파일","graft":"Graft","history":"기록"},"context":{"settings":"설정","graft_empty":"검사할 파일을 여세요","history_empty":"아직 Echo 증거 없음","history_count":"Echo 증거: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k 이동","enter_change":"enter 변경","f2_close":"f2 닫기","esc_close":"esc 닫기","j_k_scroll":"j/k 스크롤","f3_source":"f3 소스","f3_preview":"f3 미리보기","text_input":"텍스트 입력","esc_normal":"esc 일반","ctrl_s_save":"ctrl+s 저장","ctrl_t_theme":"ctrl+t 테마","tab_focus":"tab 포커스","tab_indent":"tab 들여쓰기","ctrl_l_scene_picker":"ctrl+l 장면","scene_picker":"장면 선택","ctrl_b_files":"ctrl+b 파일","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h 기록","enter_open":"enter 열기","backspace_up":"backspace 위로","ctrl_b_close":"ctrl+b 닫기","enter_jump":"enter 이동","r_refresh":"r 새로고침","ctrl_g_close":"ctrl+g 닫기","ctrl_h_close":"ctrl+h 닫기","i_insert":"i 삽입","o_open_line":"o 줄 열기","cc_line":"cc 줄","cw_word":"cw 단어","ce_word_end":"ce 단어 끝","c0_start":"c0 시작","c_end":"c$ 끝","dd_line":"dd 줄","dw_word":"dw 단어","de_word_end":"de 단어 끝","d0_start":"d0 시작","d_end":"d$ 끝","yy_line":"yy 줄","yw_word":"yw 단어","ye_word_end":"ye 단어 끝","y0_start":"y0 시작","y_end":"y$ 끝","gg_top":"gg 맨 위","esc_cancel":"esc 취소"},"evidence":{"head":"헤드","tick":"틱","anchor":"앵커","coverage":"범위","complete":"완전","partial":"부분","unavailable":"사용 불가"}},"history":{"title":"Echo 기록","empty":"아직 Echo 증거 없음","header":"# tick 종류 상태 증거 요약"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const ko: TranslationSchema = {"footer":{"mode":{"browse":"탐색","insert":"삽입","normal":"일반","preview":"미리보기","settings":"설정","files":"파일","graft":"Graft","history":"기록"},"context":{"settings":"설정","graft_empty":"검사할 파일을 여세요","history_empty":"아직 Echo 증거 없음","history_count":"Echo 증거: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k 이동","enter_change":"enter 변경","f2_close":"f2 닫기","esc_close":"esc 닫기","j_k_scroll":"j/k 스크롤","f3_source":"f3 소스","f3_preview":"f3 미리보기","text_input":"텍스트 입력","esc_normal":"esc 일반","ctrl_s_save":"ctrl+s 저장","ctrl_t_theme":"ctrl+t 테마","tab_focus":"tab 포커스","tab_indent":"tab 들여쓰기","ctrl_b_files":"ctrl+b 파일","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h 기록","enter_open":"enter 열기","backspace_up":"backspace 위로","ctrl_b_close":"ctrl+b 닫기","enter_jump":"enter 이동","r_refresh":"r 새로고침","ctrl_g_close":"ctrl+g 닫기","ctrl_h_close":"ctrl+h 닫기","i_insert":"i 삽입","o_open_line":"o 줄 열기","cc_line":"cc 줄","cw_word":"cw 단어","ce_word_end":"ce 단어 끝","c0_start":"c0 시작","c_end":"c$ 끝","dd_line":"dd 줄","dw_word":"dw 단어","de_word_end":"de 단어 끝","d0_start":"d0 시작","d_end":"d$ 끝","yy_line":"yy 줄","yw_word":"yw 단어","ye_word_end":"ye 단어 끝","y0_start":"y0 시작","y_end":"y$ 끝","gg_top":"gg 맨 위","esc_cancel":"esc 취소"},"evidence":{"head":"헤드","tick":"틱","anchor":"앵커","coverage":"범위","complete":"완전","partial":"부분","unavailable":"사용 불가"}},"history":{"title":"Echo 기록","empty":"아직 Echo 증거 없음","header":"# tick 종류 상태 증거 요약"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const ja: TranslationSchema = {"footer":{"mode":{"browse":"参照","insert":"挿入","normal":"通常","preview":"プレビュー","settings":"設定","files":"ファイル","graft":"Graft","history":"履歴"},"context":{"settings":"設定","graft_empty":"検査するファイルを開く","history_empty":"Echo 証拠はまだありません","history_count":"Echo 証拠: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k 移動","enter_change":"enter 変更","f2_close":"f2 閉じる","esc_close":"esc 閉じる","j_k_scroll":"j/k スクロール","f3_source":"f3 ソース","f3_preview":"f3 プレビュー","text_input":"テキスト入力","esc_normal":"esc 通常","ctrl_s_save":"ctrl+s 保存","ctrl_t_theme":"ctrl+t テーマ","tab_focus":"tab フォーカス","tab_indent":"tab インデント","ctrl_l_scene_picker":"ctrl+l シーン","scene_picker":"シーン選択","ctrl_b_files":"ctrl+b ファイル","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h 履歴","enter_open":"enter 開く","backspace_up":"backspace 上へ","ctrl_b_close":"ctrl+b 閉じる","enter_jump":"enter 移動","r_refresh":"r 更新","ctrl_g_close":"ctrl+g 閉じる","ctrl_h_close":"ctrl+h 閉じる","i_insert":"i 挿入","o_open_line":"o 行を開く","cc_line":"cc 行","cw_word":"cw 単語","ce_word_end":"ce 単語末","c0_start":"c0 先頭","c_end":"c$ 末尾","dd_line":"dd 行","dw_word":"dw 単語","de_word_end":"de 単語末","d0_start":"d0 先頭","d_end":"d$ 末尾","yy_line":"yy 行","yw_word":"yw 単語","ye_word_end":"ye 単語末","y0_start":"y0 先頭","y_end":"y$ 末尾","gg_top":"gg 先頭","esc_cancel":"esc 取消"},"evidence":{"head":"ヘッド","tick":"ティック","anchor":"アンカー","coverage":"範囲","complete":"完全","partial":"部分","unavailable":"利用不可"}},"history":{"title":"Echo 履歴","empty":"Echo 証拠はまだありません","header":"# tick 種類 状態 証拠 概要"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const ja: TranslationSchema = {"footer":{"mode":{"browse":"参照","insert":"挿入","normal":"通常","preview":"プレビュー","settings":"設定","files":"ファイル","graft":"Graft","history":"履歴"},"context":{"settings":"設定","graft_empty":"検査するファイルを開く","history_empty":"Echo 証拠はまだありません","history_count":"Echo 証拠: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k 移動","enter_change":"enter 変更","f2_close":"f2 閉じる","esc_close":"esc 閉じる","j_k_scroll":"j/k スクロール","f3_source":"f3 ソース","f3_preview":"f3 プレビュー","text_input":"テキスト入力","esc_normal":"esc 通常","ctrl_s_save":"ctrl+s 保存","ctrl_t_theme":"ctrl+t テーマ","tab_focus":"tab フォーカス","tab_indent":"tab インデント","ctrl_b_files":"ctrl+b ファイル","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h 履歴","enter_open":"enter 開く","backspace_up":"backspace 上へ","ctrl_b_close":"ctrl+b 閉じる","enter_jump":"enter 移動","r_refresh":"r 更新","ctrl_g_close":"ctrl+g 閉じる","ctrl_h_close":"ctrl+h 閉じる","i_insert":"i 挿入","o_open_line":"o 行を開く","cc_line":"cc 行","cw_word":"cw 単語","ce_word_end":"ce 単語末","c0_start":"c0 先頭","c_end":"c$ 末尾","dd_line":"dd 行","dw_word":"dw 単語","de_word_end":"de 単語末","d0_start":"d0 先頭","d_end":"d$ 末尾","yy_line":"yy 行","yw_word":"yw 単語","ye_word_end":"ye 単語末","y0_start":"y0 先頭","y_end":"y$ 末尾","gg_top":"gg 先頭","esc_cancel":"esc 取消"},"evidence":{"head":"ヘッド","tick":"ティック","anchor":"アンカー","coverage":"範囲","complete":"完全","partial":"部分","unavailable":"利用不可"}},"history":{"title":"Echo 履歴","empty":"Echo 証拠はまだありません","header":"# tick 種類 状態 証拠 概要"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const zhHans: TranslationSchema = {"footer":{"mode":{"browse":"浏览","insert":"插入","normal":"普通","preview":"预览","settings":"设置","files":"文件","graft":"Graft","history":"历史"},"context":{"settings":"设置","graft_empty":"打开文件以检查","history_empty":"还没有 Echo 证据","history_count":"Echo 证据: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k 移动","enter_change":"enter 更改","f2_close":"f2 关闭","esc_close":"esc 关闭","j_k_scroll":"j/k 滚动","f3_source":"f3 源码","f3_preview":"f3 预览","text_input":"文本输入","esc_normal":"esc 普通","ctrl_s_save":"ctrl+s 保存","ctrl_t_theme":"ctrl+t 主题","tab_focus":"tab 焦点","tab_indent":"tab 缩进","ctrl_l_scene_picker":"ctrl+l 场景","scene_picker":"场景选择","ctrl_b_files":"ctrl+b 文件","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h 历史","enter_open":"enter 打开","backspace_up":"backspace 上一级","ctrl_b_close":"ctrl+b 关闭","enter_jump":"enter 跳转","r_refresh":"r 刷新","ctrl_g_close":"ctrl+g 关闭","ctrl_h_close":"ctrl+h 关闭","i_insert":"i 插入","o_open_line":"o 打开行","cc_line":"cc 行","cw_word":"cw 词","ce_word_end":"ce 词尾","c0_start":"c0 开头","c_end":"c$ 结尾","dd_line":"dd 行","dw_word":"dw 词","de_word_end":"de 词尾","d0_start":"d0 开头","d_end":"d$ 结尾","yy_line":"yy 行","yw_word":"yw 词","ye_word_end":"ye 词尾","y0_start":"y0 开头","y_end":"y$ 结尾","gg_top":"gg 顶部","esc_cancel":"esc 取消"},"evidence":{"head":"头","tick":"时刻","anchor":"锚点","coverage":"覆盖","complete":"完整","partial":"部分","unavailable":"不可用"}},"history":{"title":"Echo 历史","empty":"还没有 Echo 证据","header":"# tick 类型 状态 证据 摘要"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const zhHans: TranslationSchema = {"footer":{"mode":{"browse":"浏览","insert":"插入","normal":"普通","preview":"预览","settings":"设置","files":"文件","graft":"Graft","history":"历史"},"context":{"settings":"设置","graft_empty":"打开文件以检查","history_empty":"还没有 Echo 证据","history_count":"Echo 证据: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k 移动","enter_change":"enter 更改","f2_close":"f2 关闭","esc_close":"esc 关闭","j_k_scroll":"j/k 滚动","f3_source":"f3 源码","f3_preview":"f3 预览","text_input":"文本输入","esc_normal":"esc 普通","ctrl_s_save":"ctrl+s 保存","ctrl_t_theme":"ctrl+t 主题","tab_focus":"tab 焦点","tab_indent":"tab 缩进","ctrl_b_files":"ctrl+b 文件","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h 历史","enter_open":"enter 打开","backspace_up":"backspace 上一级","ctrl_b_close":"ctrl+b 关闭","enter_jump":"enter 跳转","r_refresh":"r 刷新","ctrl_g_close":"ctrl+g 关闭","ctrl_h_close":"ctrl+h 关闭","i_insert":"i 插入","o_open_line":"o 打开行","cc_line":"cc 行","cw_word":"cw 词","ce_word_end":"ce 词尾","c0_start":"c0 开头","c_end":"c$ 结尾","dd_line":"dd 行","dw_word":"dw 词","de_word_end":"de 词尾","d0_start":"d0 开头","d_end":"d$ 结尾","yy_line":"yy 行","yw_word":"yw 词","ye_word_end":"ye 词尾","y0_start":"y0 开头","y_end":"y$ 结尾","gg_top":"gg 顶部","esc_cancel":"esc 取消"},"evidence":{"head":"头","tick":"时刻","anchor":"锚点","coverage":"覆盖","complete":"完整","partial":"部分","unavailable":"不可用"}},"history":{"title":"Echo 历史","empty":"还没有 Echo 证据","header":"# tick 类型 状态 证据 摘要"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const ptBR: TranslationSchema = {"footer":{"mode":{"browse":"navegar","insert":"inserir","normal":"normal","preview":"prévia","settings":"configurações","files":"arquivos","graft":"graft","history":"histórico"},"context":{"settings":"configurações","graft_empty":"abra um arquivo para inspecionar","history_empty":"nenhuma evidência Echo ainda","history_count":"Evidências Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k mover","enter_change":"enter alterar","f2_close":"f2 fechar","esc_close":"esc fechar","j_k_scroll":"j/k rolar","f3_source":"f3 código","f3_preview":"f3 prévia","text_input":"entrada de texto","esc_normal":"esc normal","ctrl_s_save":"ctrl+s salvar","ctrl_t_theme":"ctrl+t tema","tab_focus":"tab foco","tab_indent":"tab recuo","ctrl_l_scene_picker":"ctrl+l cena","scene_picker":"seletor de cena","ctrl_b_files":"ctrl+b arquivos","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h histórico","enter_open":"enter abrir","backspace_up":"backspace subir","ctrl_b_close":"ctrl+b fechar","enter_jump":"enter ir","r_refresh":"r atualizar","ctrl_g_close":"ctrl+g fechar","ctrl_h_close":"ctrl+h fechar","i_insert":"i inserir","o_open_line":"o abrir linha","cc_line":"cc linha","cw_word":"cw palavra","ce_word_end":"ce fim palavra","c0_start":"c0 início","c_end":"c$ fim","dd_line":"dd linha","dw_word":"dw palavra","de_word_end":"de fim palavra","d0_start":"d0 início","d_end":"d$ fim","yy_line":"yy linha","yw_word":"yw palavra","ye_word_end":"ye fim palavra","y0_start":"y0 início","y_end":"y$ fim","gg_top":"gg topo","esc_cancel":"esc cancelar"},"evidence":{"head":"cabeça","tick":"tick","anchor":"âncora","coverage":"cobertura","complete":"completa","partial":"parcial","unavailable":"indisponível"}},"history":{"title":"Histórico Echo","empty":"Nenhuma evidência Echo ainda","header":"# tick tipo status evidência resumo"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const ptBR: TranslationSchema = {"footer":{"mode":{"browse":"navegar","insert":"inserir","normal":"normal","preview":"prévia","settings":"configurações","files":"arquivos","graft":"graft","history":"histórico"},"context":{"settings":"configurações","graft_empty":"abra um arquivo para inspecionar","history_empty":"nenhuma evidência Echo ainda","history_count":"Evidências Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k mover","enter_change":"enter alterar","f2_close":"f2 fechar","esc_close":"esc fechar","j_k_scroll":"j/k rolar","f3_source":"f3 código","f3_preview":"f3 prévia","text_input":"entrada de texto","esc_normal":"esc normal","ctrl_s_save":"ctrl+s salvar","ctrl_t_theme":"ctrl+t tema","tab_focus":"tab foco","tab_indent":"tab recuo","ctrl_b_files":"ctrl+b arquivos","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h histórico","enter_open":"enter abrir","backspace_up":"backspace subir","ctrl_b_close":"ctrl+b fechar","enter_jump":"enter ir","r_refresh":"r atualizar","ctrl_g_close":"ctrl+g fechar","ctrl_h_close":"ctrl+h fechar","i_insert":"i inserir","o_open_line":"o abrir linha","cc_line":"cc linha","cw_word":"cw palavra","ce_word_end":"ce fim palavra","c0_start":"c0 início","c_end":"c$ fim","dd_line":"dd linha","dw_word":"dw palavra","de_word_end":"de fim palavra","d0_start":"d0 início","d_end":"d$ fim","yy_line":"yy linha","yw_word":"yw palavra","ye_word_end":"ye fim palavra","y0_start":"y0 início","y_end":"y$ fim","gg_top":"gg topo","esc_cancel":"esc cancelar"},"evidence":{"head":"cabeça","tick":"tick","anchor":"âncora","coverage":"cobertura","complete":"completa","partial":"parcial","unavailable":"indisponível"}},"history":{"title":"Histórico Echo","empty":"Nenhuma evidência Echo ainda","header":"# tick tipo status evidência resumo"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const ru: TranslationSchema = {"footer":{"mode":{"browse":"обзор","insert":"вставка","normal":"обычный","preview":"просмотр","settings":"настройки","files":"файлы","graft":"graft","history":"история"},"context":{"settings":"настройки","graft_empty":"откройте файл для проверки","history_empty":"пока нет доказательств Echo","history_count":"Доказательства Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k переместить","enter_change":"enter изменить","f2_close":"f2 закрыть","esc_close":"esc закрыть","j_k_scroll":"j/k прокрутка","f3_source":"f3 исходник","f3_preview":"f3 просмотр","text_input":"ввод текста","esc_normal":"esc обычный","ctrl_s_save":"ctrl+s сохранить","ctrl_t_theme":"ctrl+t тема","tab_focus":"tab фокус","tab_indent":"tab отступ","ctrl_l_scene_picker":"ctrl+l сцена","scene_picker":"выбор сцены","ctrl_b_files":"ctrl+b файлы","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h история","enter_open":"enter открыть","backspace_up":"backspace вверх","ctrl_b_close":"ctrl+b закрыть","enter_jump":"enter перейти","r_refresh":"r обновить","ctrl_g_close":"ctrl+g закрыть","ctrl_h_close":"ctrl+h закрыть","i_insert":"i вставка","o_open_line":"o открыть строку","cc_line":"cc строка","cw_word":"cw слово","ce_word_end":"ce конец слова","c0_start":"c0 начало","c_end":"c$ конец","dd_line":"dd строка","dw_word":"dw слово","de_word_end":"de конец слова","d0_start":"d0 начало","d_end":"d$ конец","yy_line":"yy строка","yw_word":"yw слово","ye_word_end":"ye конец слова","y0_start":"y0 начало","y_end":"y$ конец","gg_top":"gg верх","esc_cancel":"esc отмена"},"evidence":{"head":"вершина","tick":"тик","anchor":"якорь","coverage":"покрытие","complete":"полное","partial":"частичное","unavailable":"недоступно"}},"history":{"title":"История Echo","empty":"Пока нет доказательств Echo","header":"# tick тип статус доказательство сводка"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const ru: TranslationSchema = {"footer":{"mode":{"browse":"обзор","insert":"вставка","normal":"обычный","preview":"просмотр","settings":"настройки","files":"файлы","graft":"graft","history":"история"},"context":{"settings":"настройки","graft_empty":"откройте файл для проверки","history_empty":"пока нет доказательств Echo","history_count":"Доказательства Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k переместить","enter_change":"enter изменить","f2_close":"f2 закрыть","esc_close":"esc закрыть","j_k_scroll":"j/k прокрутка","f3_source":"f3 исходник","f3_preview":"f3 просмотр","text_input":"ввод текста","esc_normal":"esc обычный","ctrl_s_save":"ctrl+s сохранить","ctrl_t_theme":"ctrl+t тема","tab_focus":"tab фокус","tab_indent":"tab отступ","ctrl_b_files":"ctrl+b файлы","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h история","enter_open":"enter открыть","backspace_up":"backspace вверх","ctrl_b_close":"ctrl+b закрыть","enter_jump":"enter перейти","r_refresh":"r обновить","ctrl_g_close":"ctrl+g закрыть","ctrl_h_close":"ctrl+h закрыть","i_insert":"i вставка","o_open_line":"o открыть строку","cc_line":"cc строка","cw_word":"cw слово","ce_word_end":"ce конец слова","c0_start":"c0 начало","c_end":"c$ конец","dd_line":"dd строка","dw_word":"dw слово","de_word_end":"de конец слова","d0_start":"d0 начало","d_end":"d$ конец","yy_line":"yy строка","yw_word":"yw слово","ye_word_end":"ye конец слова","y0_start":"y0 начало","y_end":"y$ конец","gg_top":"gg верх","esc_cancel":"esc отмена"},"evidence":{"head":"вершина","tick":"тик","anchor":"якорь","coverage":"покрытие","complete":"полное","partial":"частичное","unavailable":"недоступно"}},"history":{"title":"История Echo","empty":"Пока нет доказательств Echo","header":"# tick тип статус доказательство сводка"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const ar: TranslationSchema = {"footer":{"mode":{"browse":"تصفح","insert":"إدراج","normal":"عادي","preview":"معاينة","settings":"إعدادات","files":"ملفات","graft":"Graft","history":"السجل"},"context":{"settings":"إعدادات","graft_empty":"افتح ملفا لفحصه","history_empty":"لا توجد أدلة Echo بعد","history_count":"أدلة Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k تحريك","enter_change":"enter تغيير","f2_close":"f2 إغلاق","esc_close":"esc إغلاق","j_k_scroll":"j/k تمرير","f3_source":"f3 المصدر","f3_preview":"f3 معاينة","text_input":"إدخال نص","esc_normal":"esc عادي","ctrl_s_save":"ctrl+s حفظ","ctrl_t_theme":"ctrl+t سمة","tab_focus":"tab تركيز","tab_indent":"tab إزاحة","ctrl_l_scene_picker":"ctrl+l مشهد","scene_picker":"اختيار مشهد","ctrl_b_files":"ctrl+b ملفات","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h سجل","enter_open":"enter فتح","backspace_up":"backspace أعلى","ctrl_b_close":"ctrl+b إغلاق","enter_jump":"enter انتقال","r_refresh":"r تحديث","ctrl_g_close":"ctrl+g إغلاق","ctrl_h_close":"ctrl+h إغلاق","i_insert":"i إدراج","o_open_line":"o فتح سطر","cc_line":"cc سطر","cw_word":"cw كلمة","ce_word_end":"ce نهاية كلمة","c0_start":"c0 بداية","c_end":"c$ نهاية","dd_line":"dd سطر","dw_word":"dw كلمة","de_word_end":"de نهاية كلمة","d0_start":"d0 بداية","d_end":"d$ نهاية","yy_line":"yy سطر","yw_word":"yw كلمة","ye_word_end":"ye نهاية كلمة","y0_start":"y0 بداية","y_end":"y$ نهاية","gg_top":"gg أعلى","esc_cancel":"esc إلغاء"},"evidence":{"head":"الرأس","tick":"النبضة","anchor":"المرساة","coverage":"التغطية","complete":"كاملة","partial":"جزئية","unavailable":"غير متاحة"}},"history":{"title":"سجل Echo","empty":"لا توجد أدلة Echo بعد","header":"# tick النوع الحالة الدليل الملخص"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const ar: TranslationSchema = {"footer":{"mode":{"browse":"تصفح","insert":"إدراج","normal":"عادي","preview":"معاينة","settings":"إعدادات","files":"ملفات","graft":"Graft","history":"السجل"},"context":{"settings":"إعدادات","graft_empty":"افتح ملفا لفحصه","history_empty":"لا توجد أدلة Echo بعد","history_count":"أدلة Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k تحريك","enter_change":"enter تغيير","f2_close":"f2 إغلاق","esc_close":"esc إغلاق","j_k_scroll":"j/k تمرير","f3_source":"f3 المصدر","f3_preview":"f3 معاينة","text_input":"إدخال نص","esc_normal":"esc عادي","ctrl_s_save":"ctrl+s حفظ","ctrl_t_theme":"ctrl+t سمة","tab_focus":"tab تركيز","tab_indent":"tab إزاحة","ctrl_b_files":"ctrl+b ملفات","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h سجل","enter_open":"enter فتح","backspace_up":"backspace أعلى","ctrl_b_close":"ctrl+b إغلاق","enter_jump":"enter انتقال","r_refresh":"r تحديث","ctrl_g_close":"ctrl+g إغلاق","ctrl_h_close":"ctrl+h إغلاق","i_insert":"i إدراج","o_open_line":"o فتح سطر","cc_line":"cc سطر","cw_word":"cw كلمة","ce_word_end":"ce نهاية كلمة","c0_start":"c0 بداية","c_end":"c$ نهاية","dd_line":"dd سطر","dw_word":"dw كلمة","de_word_end":"de نهاية كلمة","d0_start":"d0 بداية","d_end":"d$ نهاية","yy_line":"yy سطر","yw_word":"yw كلمة","ye_word_end":"ye نهاية كلمة","y0_start":"y0 بداية","y_end":"y$ نهاية","gg_top":"gg أعلى","esc_cancel":"esc إلغاء"},"evidence":{"head":"الرأس","tick":"النبضة","anchor":"المرساة","coverage":"التغطية","complete":"كاملة","partial":"جزئية","unavailable":"غير متاحة"}},"history":{"title":"سجل Echo","empty":"لا توجد أدلة Echo بعد","header":"# tick النوع الحالة الدليل الملخص"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const id: TranslationSchema = {"footer":{"mode":{"browse":"jelajah","insert":"sisip","normal":"normal","preview":"pratinjau","settings":"pengaturan","files":"berkas","graft":"graft","history":"riwayat"},"context":{"settings":"pengaturan","graft_empty":"buka berkas untuk diperiksa","history_empty":"belum ada bukti Echo","history_count":"Bukti Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k pindah","enter_change":"enter ubah","f2_close":"f2 tutup","esc_close":"esc tutup","j_k_scroll":"j/k gulir","f3_source":"f3 sumber","f3_preview":"f3 pratinjau","text_input":"input teks","esc_normal":"esc normal","ctrl_s_save":"ctrl+s simpan","ctrl_t_theme":"ctrl+t tema","tab_focus":"tab fokus","tab_indent":"tab inden","ctrl_l_scene_picker":"ctrl+l adegan","scene_picker":"pilih adegan","ctrl_b_files":"ctrl+b berkas","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h riwayat","enter_open":"enter buka","backspace_up":"backspace naik","ctrl_b_close":"ctrl+b tutup","enter_jump":"enter lompat","r_refresh":"r segarkan","ctrl_g_close":"ctrl+g tutup","ctrl_h_close":"ctrl+h tutup","i_insert":"i sisip","o_open_line":"o buka baris","cc_line":"cc baris","cw_word":"cw kata","ce_word_end":"ce akhir kata","c0_start":"c0 awal","c_end":"c$ akhir","dd_line":"dd baris","dw_word":"dw kata","de_word_end":"de akhir kata","d0_start":"d0 awal","d_end":"d$ akhir","yy_line":"yy baris","yw_word":"yw kata","ye_word_end":"ye akhir kata","y0_start":"y0 awal","y_end":"y$ akhir","gg_top":"gg atas","esc_cancel":"esc batal"},"evidence":{"head":"kepala","tick":"tick","anchor":"jangkar","coverage":"cakupan","complete":"lengkap","partial":"sebagian","unavailable":"tidak tersedia"}},"history":{"title":"Riwayat Echo","empty":"Belum ada bukti Echo","header":"# tick jenis status bukti ringkasan"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const id: TranslationSchema = {"footer":{"mode":{"browse":"jelajah","insert":"sisip","normal":"normal","preview":"pratinjau","settings":"pengaturan","files":"berkas","graft":"graft","history":"riwayat"},"context":{"settings":"pengaturan","graft_empty":"buka berkas untuk diperiksa","history_empty":"belum ada bukti Echo","history_count":"Bukti Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k pindah","enter_change":"enter ubah","f2_close":"f2 tutup","esc_close":"esc tutup","j_k_scroll":"j/k gulir","f3_source":"f3 sumber","f3_preview":"f3 pratinjau","text_input":"input teks","esc_normal":"esc normal","ctrl_s_save":"ctrl+s simpan","ctrl_t_theme":"ctrl+t tema","tab_focus":"tab fokus","tab_indent":"tab inden","ctrl_b_files":"ctrl+b berkas","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h riwayat","enter_open":"enter buka","backspace_up":"backspace naik","ctrl_b_close":"ctrl+b tutup","enter_jump":"enter lompat","r_refresh":"r segarkan","ctrl_g_close":"ctrl+g tutup","ctrl_h_close":"ctrl+h tutup","i_insert":"i sisip","o_open_line":"o buka baris","cc_line":"cc baris","cw_word":"cw kata","ce_word_end":"ce akhir kata","c0_start":"c0 awal","c_end":"c$ akhir","dd_line":"dd baris","dw_word":"dw kata","de_word_end":"de akhir kata","d0_start":"d0 awal","d_end":"d$ akhir","yy_line":"yy baris","yw_word":"yw kata","ye_word_end":"ye akhir kata","y0_start":"y0 awal","y_end":"y$ akhir","gg_top":"gg atas","esc_cancel":"esc batal"},"evidence":{"head":"kepala","tick":"tick","anchor":"jangkar","coverage":"cakupan","complete":"lengkap","partial":"sebagian","unavailable":"tidak tersedia"}},"history":{"title":"Riwayat Echo","empty":"Belum ada bukti Echo","header":"# tick jenis status bukti ringkasan"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const tr: TranslationSchema = {"footer":{"mode":{"browse":"gözat","insert":"ekle","normal":"normal","preview":"önizleme","settings":"ayarlar","files":"dosyalar","graft":"graft","history":"geçmiş"},"context":{"settings":"ayarlar","graft_empty":"incelemek için dosya aç","history_empty":"henüz Echo kanıtı yok","history_count":"Echo kanıtı: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k taşı","enter_change":"enter değiştir","f2_close":"f2 kapat","esc_close":"esc kapat","j_k_scroll":"j/k kaydır","f3_source":"f3 kaynak","f3_preview":"f3 önizleme","text_input":"metin girişi","esc_normal":"esc normal","ctrl_s_save":"ctrl+s kaydet","ctrl_t_theme":"ctrl+t tema","tab_focus":"tab odak","tab_indent":"tab girinti","ctrl_l_scene_picker":"ctrl+l sahne","scene_picker":"sahne seçici","ctrl_b_files":"ctrl+b dosyalar","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h geçmiş","enter_open":"enter aç","backspace_up":"backspace yukarı","ctrl_b_close":"ctrl+b kapat","enter_jump":"enter git","r_refresh":"r yenile","ctrl_g_close":"ctrl+g kapat","ctrl_h_close":"ctrl+h kapat","i_insert":"i ekle","o_open_line":"o satır aç","cc_line":"cc satır","cw_word":"cw kelime","ce_word_end":"ce kelime sonu","c0_start":"c0 başlangıç","c_end":"c$ son","dd_line":"dd satır","dw_word":"dw kelime","de_word_end":"de kelime sonu","d0_start":"d0 başlangıç","d_end":"d$ son","yy_line":"yy satır","yw_word":"yw kelime","ye_word_end":"ye kelime sonu","y0_start":"y0 başlangıç","y_end":"y$ son","gg_top":"gg üst","esc_cancel":"esc iptal"},"evidence":{"head":"baş","tick":"tik","anchor":"çapa","coverage":"kapsam","complete":"tam","partial":"kısmi","unavailable":"kullanılamıyor"}},"history":{"title":"Echo Geçmişi","empty":"Henüz Echo kanıtı yok","header":"# tick tür durum kanıt özet"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const tr: TranslationSchema = {"footer":{"mode":{"browse":"gözat","insert":"ekle","normal":"normal","preview":"önizleme","settings":"ayarlar","files":"dosyalar","graft":"graft","history":"geçmiş"},"context":{"settings":"ayarlar","graft_empty":"incelemek için dosya aç","history_empty":"henüz Echo kanıtı yok","history_count":"Echo kanıtı: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k taşı","enter_change":"enter değiştir","f2_close":"f2 kapat","esc_close":"esc kapat","j_k_scroll":"j/k kaydır","f3_source":"f3 kaynak","f3_preview":"f3 önizleme","text_input":"metin girişi","esc_normal":"esc normal","ctrl_s_save":"ctrl+s kaydet","ctrl_t_theme":"ctrl+t tema","tab_focus":"tab odak","tab_indent":"tab girinti","ctrl_b_files":"ctrl+b dosyalar","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h geçmiş","enter_open":"enter aç","backspace_up":"backspace yukarı","ctrl_b_close":"ctrl+b kapat","enter_jump":"enter git","r_refresh":"r yenile","ctrl_g_close":"ctrl+g kapat","ctrl_h_close":"ctrl+h kapat","i_insert":"i ekle","o_open_line":"o satır aç","cc_line":"cc satır","cw_word":"cw kelime","ce_word_end":"ce kelime sonu","c0_start":"c0 başlangıç","c_end":"c$ son","dd_line":"dd satır","dw_word":"dw kelime","de_word_end":"de kelime sonu","d0_start":"d0 başlangıç","d_end":"d$ son","yy_line":"yy satır","yw_word":"yw kelime","ye_word_end":"ye kelime sonu","y0_start":"y0 başlangıç","y_end":"y$ son","gg_top":"gg üst","esc_cancel":"esc iptal"},"evidence":{"head":"baş","tick":"tik","anchor":"çapa","coverage":"kapsam","complete":"tam","partial":"kısmi","unavailable":"kullanılamıyor"}},"history":{"title":"Echo Geçmişi","empty":"Henüz Echo kanıtı yok","header":"# tick tür durum kanıt özet"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const hi: TranslationSchema = {"footer":{"mode":{"browse":"ब्राउज़","insert":"सम्मिलित","normal":"सामान्य","preview":"पूर्वावलोकन","settings":"सेटिंग","files":"फाइलें","graft":"Graft","history":"इतिहास"},"context":{"settings":"सेटिंग","graft_empty":"जांचने के लिए फाइल खोलें","history_empty":"अभी कोई Echo प्रमाण नहीं","history_count":"Echo प्रमाण: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k चलें","enter_change":"enter बदलें","f2_close":"f2 बंद","esc_close":"esc बंद","j_k_scroll":"j/k स्क्रोल","f3_source":"f3 स्रोत","f3_preview":"f3 पूर्वावलोकन","text_input":"पाठ इनपुट","esc_normal":"esc सामान्य","ctrl_s_save":"ctrl+s सहेजें","ctrl_t_theme":"ctrl+t थीम","tab_focus":"tab फोकस","tab_indent":"tab इंडेंट","ctrl_l_scene_picker":"ctrl+l दृश्य","scene_picker":"दृश्य चयन","ctrl_b_files":"ctrl+b फाइलें","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h इतिहास","enter_open":"enter खोलें","backspace_up":"backspace ऊपर","ctrl_b_close":"ctrl+b बंद","enter_jump":"enter जाएं","r_refresh":"r ताज़ा","ctrl_g_close":"ctrl+g बंद","ctrl_h_close":"ctrl+h बंद","i_insert":"i सम्मिलित","o_open_line":"o पंक्ति खोलें","cc_line":"cc पंक्ति","cw_word":"cw शब्द","ce_word_end":"ce शब्द अंत","c0_start":"c0 शुरुआत","c_end":"c$ अंत","dd_line":"dd पंक्ति","dw_word":"dw शब्द","de_word_end":"de शब्द अंत","d0_start":"d0 शुरुआत","d_end":"d$ अंत","yy_line":"yy पंक्ति","yw_word":"yw शब्द","ye_word_end":"ye शब्द अंत","y0_start":"y0 शुरुआत","y_end":"y$ अंत","gg_top":"gg ऊपर","esc_cancel":"esc रद्द"},"evidence":{"head":"शीर्ष","tick":"टिक","anchor":"एंकर","coverage":"कवरेज","complete":"पूर्ण","partial":"आंशिक","unavailable":"अनुपलब्ध"}},"history":{"title":"Echo इतिहास","empty":"अभी कोई Echo प्रमाण नहीं","header":"# tick प्रकार स्थिति प्रमाण सारांश"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const hi: TranslationSchema = {"footer":{"mode":{"browse":"ब्राउज़","insert":"सम्मिलित","normal":"सामान्य","preview":"पूर्वावलोकन","settings":"सेटिंग","files":"फाइलें","graft":"Graft","history":"इतिहास"},"context":{"settings":"सेटिंग","graft_empty":"जांचने के लिए फाइल खोलें","history_empty":"अभी कोई Echo प्रमाण नहीं","history_count":"Echo प्रमाण: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k चलें","enter_change":"enter बदलें","f2_close":"f2 बंद","esc_close":"esc बंद","j_k_scroll":"j/k स्क्रोल","f3_source":"f3 स्रोत","f3_preview":"f3 पूर्वावलोकन","text_input":"पाठ इनपुट","esc_normal":"esc सामान्य","ctrl_s_save":"ctrl+s सहेजें","ctrl_t_theme":"ctrl+t थीम","tab_focus":"tab फोकस","tab_indent":"tab इंडेंट","ctrl_b_files":"ctrl+b फाइलें","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h इतिहास","enter_open":"enter खोलें","backspace_up":"backspace ऊपर","ctrl_b_close":"ctrl+b बंद","enter_jump":"enter जाएं","r_refresh":"r ताज़ा","ctrl_g_close":"ctrl+g बंद","ctrl_h_close":"ctrl+h बंद","i_insert":"i सम्मिलित","o_open_line":"o पंक्ति खोलें","cc_line":"cc पंक्ति","cw_word":"cw शब्द","ce_word_end":"ce शब्द अंत","c0_start":"c0 शुरुआत","c_end":"c$ अंत","dd_line":"dd पंक्ति","dw_word":"dw शब्द","de_word_end":"de शब्द अंत","d0_start":"d0 शुरुआत","d_end":"d$ अंत","yy_line":"yy पंक्ति","yw_word":"yw शब्द","ye_word_end":"ye शब्द अंत","y0_start":"y0 शुरुआत","y_end":"y$ अंत","gg_top":"gg ऊपर","esc_cancel":"esc रद्द"},"evidence":{"head":"शीर्ष","tick":"टिक","anchor":"एंकर","coverage":"कवरेज","complete":"पूर्ण","partial":"आंशिक","unavailable":"अनुपलब्ध"}},"history":{"title":"Echo इतिहास","empty":"अभी कोई Echo प्रमाण नहीं","header":"# tick प्रकार स्थिति प्रमाण सारांश"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const nl: TranslationSchema = {"footer":{"mode":{"browse":"bladeren","insert":"invoegen","normal":"normaal","preview":"voorbeeld","settings":"instellingen","files":"bestanden","graft":"graft","history":"geschiedenis"},"context":{"settings":"instellingen","graft_empty":"open een bestand om te inspecteren","history_empty":"nog geen Echo-bewijs","history_count":"Echo-bewijs: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k bewegen","enter_change":"enter wijzigen","f2_close":"f2 sluiten","esc_close":"esc sluiten","j_k_scroll":"j/k scrollen","f3_source":"f3 bron","f3_preview":"f3 voorbeeld","text_input":"tekstinvoer","esc_normal":"esc normaal","ctrl_s_save":"ctrl+s opslaan","ctrl_t_theme":"ctrl+t thema","tab_focus":"tab focus","tab_indent":"tab inspringen","ctrl_l_scene_picker":"ctrl+l scène","scene_picker":"scènekiezer","ctrl_b_files":"ctrl+b bestanden","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h geschiedenis","enter_open":"enter openen","backspace_up":"backspace omhoog","ctrl_b_close":"ctrl+b sluiten","enter_jump":"enter springen","r_refresh":"r vernieuwen","ctrl_g_close":"ctrl+g sluiten","ctrl_h_close":"ctrl+h sluiten","i_insert":"i invoegen","o_open_line":"o regel openen","cc_line":"cc regel","cw_word":"cw woord","ce_word_end":"ce woord-einde","c0_start":"c0 begin","c_end":"c$ einde","dd_line":"dd regel","dw_word":"dw woord","de_word_end":"de woord-einde","d0_start":"d0 begin","d_end":"d$ einde","yy_line":"yy regel","yw_word":"yw woord","ye_word_end":"ye woord-einde","y0_start":"y0 begin","y_end":"y$ einde","gg_top":"gg boven","esc_cancel":"esc annuleren"},"evidence":{"head":"kop","tick":"tick","anchor":"anker","coverage":"dekking","complete":"volledig","partial":"gedeeltelijk","unavailable":"niet beschikbaar"}},"history":{"title":"Echo-geschiedenis","empty":"Nog geen Echo-bewijs","header":"# tick soort status bewijs samenvatting"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const nl: TranslationSchema = {"footer":{"mode":{"browse":"bladeren","insert":"invoegen","normal":"normaal","preview":"voorbeeld","settings":"instellingen","files":"bestanden","graft":"graft","history":"geschiedenis"},"context":{"settings":"instellingen","graft_empty":"open een bestand om te inspecteren","history_empty":"nog geen Echo-bewijs","history_count":"Echo-bewijs: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k bewegen","enter_change":"enter wijzigen","f2_close":"f2 sluiten","esc_close":"esc sluiten","j_k_scroll":"j/k scrollen","f3_source":"f3 bron","f3_preview":"f3 voorbeeld","text_input":"tekstinvoer","esc_normal":"esc normaal","ctrl_s_save":"ctrl+s opslaan","ctrl_t_theme":"ctrl+t thema","tab_focus":"tab focus","tab_indent":"tab inspringen","ctrl_b_files":"ctrl+b bestanden","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h geschiedenis","enter_open":"enter openen","backspace_up":"backspace omhoog","ctrl_b_close":"ctrl+b sluiten","enter_jump":"enter springen","r_refresh":"r vernieuwen","ctrl_g_close":"ctrl+g sluiten","ctrl_h_close":"ctrl+h sluiten","i_insert":"i invoegen","o_open_line":"o regel openen","cc_line":"cc regel","cw_word":"cw woord","ce_word_end":"ce woord-einde","c0_start":"c0 begin","c_end":"c$ einde","dd_line":"dd regel","dw_word":"dw woord","de_word_end":"de woord-einde","d0_start":"d0 begin","d_end":"d$ einde","yy_line":"yy regel","yw_word":"yw woord","ye_word_end":"ye woord-einde","y0_start":"y0 begin","y_end":"y$ einde","gg_top":"gg boven","esc_cancel":"esc annuleren"},"evidence":{"head":"kop","tick":"tick","anchor":"anker","coverage":"dekking","complete":"volledig","partial":"gedeeltelijk","unavailable":"niet beschikbaar"}},"history":{"title":"Echo-geschiedenis","empty":"Nog geen Echo-bewijs","header":"# tick soort status bewijs samenvatting"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const pl: TranslationSchema = {"footer":{"mode":{"browse":"przeglądaj","insert":"wstaw","normal":"normalny","preview":"podgląd","settings":"ustawienia","files":"pliki","graft":"graft","history":"historia"},"context":{"settings":"ustawienia","graft_empty":"otwórz plik do sprawdzenia","history_empty":"brak jeszcze dowodów Echo","history_count":"Dowody Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k przesuń","enter_change":"enter zmień","f2_close":"f2 zamknij","esc_close":"esc zamknij","j_k_scroll":"j/k przewiń","f3_source":"f3 źródło","f3_preview":"f3 podgląd","text_input":"wpisywanie tekstu","esc_normal":"esc normalny","ctrl_s_save":"ctrl+s zapisz","ctrl_t_theme":"ctrl+t motyw","tab_focus":"tab fokus","tab_indent":"tab wcięcie","ctrl_l_scene_picker":"ctrl+l scena","scene_picker":"wybór sceny","ctrl_b_files":"ctrl+b pliki","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h historia","enter_open":"enter otwórz","backspace_up":"backspace w górę","ctrl_b_close":"ctrl+b zamknij","enter_jump":"enter skocz","r_refresh":"r odśwież","ctrl_g_close":"ctrl+g zamknij","ctrl_h_close":"ctrl+h zamknij","i_insert":"i wstaw","o_open_line":"o otwórz linię","cc_line":"cc linia","cw_word":"cw słowo","ce_word_end":"ce koniec słowa","c0_start":"c0 początek","c_end":"c$ koniec","dd_line":"dd linia","dw_word":"dw słowo","de_word_end":"de koniec słowa","d0_start":"d0 początek","d_end":"d$ koniec","yy_line":"yy linia","yw_word":"yw słowo","ye_word_end":"ye koniec słowa","y0_start":"y0 początek","y_end":"y$ koniec","gg_top":"gg góra","esc_cancel":"esc anuluj"},"evidence":{"head":"głowa","tick":"takt","anchor":"kotwica","coverage":"pokrycie","complete":"pełne","partial":"częściowe","unavailable":"niedostępne"}},"history":{"title":"Historia Echo","empty":"Brak jeszcze dowodów Echo","header":"# tick typ status dowód podsumowanie"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const pl: TranslationSchema = {"footer":{"mode":{"browse":"przeglądaj","insert":"wstaw","normal":"normalny","preview":"podgląd","settings":"ustawienia","files":"pliki","graft":"graft","history":"historia"},"context":{"settings":"ustawienia","graft_empty":"otwórz plik do sprawdzenia","history_empty":"brak jeszcze dowodów Echo","history_count":"Dowody Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k przesuń","enter_change":"enter zmień","f2_close":"f2 zamknij","esc_close":"esc zamknij","j_k_scroll":"j/k przewiń","f3_source":"f3 źródło","f3_preview":"f3 podgląd","text_input":"wpisywanie tekstu","esc_normal":"esc normalny","ctrl_s_save":"ctrl+s zapisz","ctrl_t_theme":"ctrl+t motyw","tab_focus":"tab fokus","tab_indent":"tab wcięcie","ctrl_b_files":"ctrl+b pliki","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h historia","enter_open":"enter otwórz","backspace_up":"backspace w górę","ctrl_b_close":"ctrl+b zamknij","enter_jump":"enter skocz","r_refresh":"r odśwież","ctrl_g_close":"ctrl+g zamknij","ctrl_h_close":"ctrl+h zamknij","i_insert":"i wstaw","o_open_line":"o otwórz linię","cc_line":"cc linia","cw_word":"cw słowo","ce_word_end":"ce koniec słowa","c0_start":"c0 początek","c_end":"c$ koniec","dd_line":"dd linia","dw_word":"dw słowo","de_word_end":"de koniec słowa","d0_start":"d0 początek","d_end":"d$ koniec","yy_line":"yy linia","yw_word":"yw słowo","ye_word_end":"ye koniec słowa","y0_start":"y0 początek","y_end":"y$ koniec","gg_top":"gg góra","esc_cancel":"esc anuluj"},"evidence":{"head":"głowa","tick":"takt","anchor":"kotwica","coverage":"pokrycie","complete":"pełne","partial":"częściowe","unavailable":"niedostępne"}},"history":{"title":"Historia Echo","empty":"Brak jeszcze dowodów Echo","header":"# tick typ status dowód podsumowanie"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const vi: TranslationSchema = {"footer":{"mode":{"browse":"duyệt","insert":"chèn","normal":"thường","preview":"xem trước","settings":"cài đặt","files":"tệp","graft":"graft","history":"lịch sử"},"context":{"settings":"cài đặt","graft_empty":"mở tệp để kiểm tra","history_empty":"chưa có bằng chứng Echo","history_count":"Bằng chứng Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k di chuyển","enter_change":"enter đổi","f2_close":"f2 đóng","esc_close":"esc đóng","j_k_scroll":"j/k cuộn","f3_source":"f3 nguồn","f3_preview":"f3 xem trước","text_input":"nhập văn bản","esc_normal":"esc thường","ctrl_s_save":"ctrl+s lưu","ctrl_t_theme":"ctrl+t giao diện","tab_focus":"tab tiêu điểm","tab_indent":"tab thụt lề","ctrl_l_scene_picker":"ctrl+l cảnh","scene_picker":"chọn cảnh","ctrl_b_files":"ctrl+b tệp","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h lịch sử","enter_open":"enter mở","backspace_up":"backspace lên","ctrl_b_close":"ctrl+b đóng","enter_jump":"enter nhảy","r_refresh":"r làm mới","ctrl_g_close":"ctrl+g đóng","ctrl_h_close":"ctrl+h đóng","i_insert":"i chèn","o_open_line":"o mở dòng","cc_line":"cc dòng","cw_word":"cw từ","ce_word_end":"ce cuối từ","c0_start":"c0 đầu","c_end":"c$ cuối","dd_line":"dd dòng","dw_word":"dw từ","de_word_end":"de cuối từ","d0_start":"d0 đầu","d_end":"d$ cuối","yy_line":"yy dòng","yw_word":"yw từ","ye_word_end":"ye cuối từ","y0_start":"y0 đầu","y_end":"y$ cuối","gg_top":"gg đầu","esc_cancel":"esc hủy"},"evidence":{"head":"đầu","tick":"nhịp","anchor":"neo","coverage":"độ phủ","complete":"đầy đủ","partial":"một phần","unavailable":"không khả dụng"}},"history":{"title":"Lịch sử Echo","empty":"Chưa có bằng chứng Echo","header":"# tick loại trạng thái bằng chứng tóm tắt"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const vi: TranslationSchema = {"footer":{"mode":{"browse":"duyệt","insert":"chèn","normal":"thường","preview":"xem trước","settings":"cài đặt","files":"tệp","graft":"graft","history":"lịch sử"},"context":{"settings":"cài đặt","graft_empty":"mở tệp để kiểm tra","history_empty":"chưa có bằng chứng Echo","history_count":"Bằng chứng Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k di chuyển","enter_change":"enter đổi","f2_close":"f2 đóng","esc_close":"esc đóng","j_k_scroll":"j/k cuộn","f3_source":"f3 nguồn","f3_preview":"f3 xem trước","text_input":"nhập văn bản","esc_normal":"esc thường","ctrl_s_save":"ctrl+s lưu","ctrl_t_theme":"ctrl+t giao diện","tab_focus":"tab tiêu điểm","tab_indent":"tab thụt lề","ctrl_b_files":"ctrl+b tệp","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h lịch sử","enter_open":"enter mở","backspace_up":"backspace lên","ctrl_b_close":"ctrl+b đóng","enter_jump":"enter nhảy","r_refresh":"r làm mới","ctrl_g_close":"ctrl+g đóng","ctrl_h_close":"ctrl+h đóng","i_insert":"i chèn","o_open_line":"o mở dòng","cc_line":"cc dòng","cw_word":"cw từ","ce_word_end":"ce cuối từ","c0_start":"c0 đầu","c_end":"c$ cuối","dd_line":"dd dòng","dw_word":"dw từ","de_word_end":"de cuối từ","d0_start":"d0 đầu","d_end":"d$ cuối","yy_line":"yy dòng","yw_word":"yw từ","ye_word_end":"ye cuối từ","y0_start":"y0 đầu","y_end":"y$ cuối","gg_top":"gg đầu","esc_cancel":"esc hủy"},"evidence":{"head":"đầu","tick":"nhịp","anchor":"neo","coverage":"độ phủ","complete":"đầy đủ","partial":"một phần","unavailable":"không khả dụng"}},"history":{"title":"Lịch sử Echo","empty":"Chưa có bằng chứng Echo","header":"# tick loại trạng thái bằng chứng tóm tắt"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const th: TranslationSchema = {"footer":{"mode":{"browse":"เรียกดู","insert":"แทรก","normal":"ปกติ","preview":"ดูตัวอย่าง","settings":"ตั้งค่า","files":"ไฟล์","graft":"Graft","history":"ประวัติ"},"context":{"settings":"ตั้งค่า","graft_empty":"เปิดไฟล์เพื่อตรวจสอบ","history_empty":"ยังไม่มีหลักฐาน Echo","history_count":"หลักฐาน Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k ย้าย","enter_change":"enter เปลี่ยน","f2_close":"f2 ปิด","esc_close":"esc ปิด","j_k_scroll":"j/k เลื่อน","f3_source":"f3 ต้นฉบับ","f3_preview":"f3 ดูตัวอย่าง","text_input":"ป้อนข้อความ","esc_normal":"esc ปกติ","ctrl_s_save":"ctrl+s บันทึก","ctrl_t_theme":"ctrl+t ธีม","tab_focus":"tab โฟกัส","tab_indent":"tab เยื้อง","ctrl_l_scene_picker":"ctrl+l ฉาก","scene_picker":"เลือกฉาก","ctrl_b_files":"ctrl+b ไฟล์","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h ประวัติ","enter_open":"enter เปิด","backspace_up":"backspace ขึ้น","ctrl_b_close":"ctrl+b ปิด","enter_jump":"enter ไป","r_refresh":"r รีเฟรช","ctrl_g_close":"ctrl+g ปิด","ctrl_h_close":"ctrl+h ปิด","i_insert":"i แทรก","o_open_line":"o เปิดบรรทัด","cc_line":"cc บรรทัด","cw_word":"cw คำ","ce_word_end":"ce จบคำ","c0_start":"c0 เริ่ม","c_end":"c$ จบ","dd_line":"dd บรรทัด","dw_word":"dw คำ","de_word_end":"de จบคำ","d0_start":"d0 เริ่ม","d_end":"d$ จบ","yy_line":"yy บรรทัด","yw_word":"yw คำ","ye_word_end":"ye จบคำ","y0_start":"y0 เริ่ม","y_end":"y$ จบ","gg_top":"gg บน","esc_cancel":"esc ยกเลิก"},"evidence":{"head":"เฮด","tick":"ทิก","anchor":"จุดยึด","coverage":"ความครอบคลุม","complete":"ครบถ้วน","partial":"บางส่วน","unavailable":"ไม่พร้อมใช้งาน"}},"history":{"title":"ประวัติ Echo","empty":"ยังไม่มีหลักฐาน Echo","header":"# tick ชนิด สถานะ หลักฐาน สรุป"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const th: TranslationSchema = {"footer":{"mode":{"browse":"เรียกดู","insert":"แทรก","normal":"ปกติ","preview":"ดูตัวอย่าง","settings":"ตั้งค่า","files":"ไฟล์","graft":"Graft","history":"ประวัติ"},"context":{"settings":"ตั้งค่า","graft_empty":"เปิดไฟล์เพื่อตรวจสอบ","history_empty":"ยังไม่มีหลักฐาน Echo","history_count":"หลักฐาน Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k ย้าย","enter_change":"enter เปลี่ยน","f2_close":"f2 ปิด","esc_close":"esc ปิด","j_k_scroll":"j/k เลื่อน","f3_source":"f3 ต้นฉบับ","f3_preview":"f3 ดูตัวอย่าง","text_input":"ป้อนข้อความ","esc_normal":"esc ปกติ","ctrl_s_save":"ctrl+s บันทึก","ctrl_t_theme":"ctrl+t ธีม","tab_focus":"tab โฟกัส","tab_indent":"tab เยื้อง","ctrl_b_files":"ctrl+b ไฟล์","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h ประวัติ","enter_open":"enter เปิด","backspace_up":"backspace ขึ้น","ctrl_b_close":"ctrl+b ปิด","enter_jump":"enter ไป","r_refresh":"r รีเฟรช","ctrl_g_close":"ctrl+g ปิด","ctrl_h_close":"ctrl+h ปิด","i_insert":"i แทรก","o_open_line":"o เปิดบรรทัด","cc_line":"cc บรรทัด","cw_word":"cw คำ","ce_word_end":"ce จบคำ","c0_start":"c0 เริ่ม","c_end":"c$ จบ","dd_line":"dd บรรทัด","dw_word":"dw คำ","de_word_end":"de จบคำ","d0_start":"d0 เริ่ม","d_end":"d$ จบ","yy_line":"yy บรรทัด","yw_word":"yw คำ","ye_word_end":"ye จบคำ","y0_start":"y0 เริ่ม","y_end":"y$ จบ","gg_top":"gg บน","esc_cancel":"esc ยกเลิก"},"evidence":{"head":"เฮด","tick":"ทิก","anchor":"จุดยึด","coverage":"ความครอบคลุม","complete":"ครบถ้วน","partial":"บางส่วน","unavailable":"ไม่พร้อมใช้งาน"}},"history":{"title":"ประวัติ Echo","empty":"ยังไม่มีหลักฐาน Echo","header":"# tick ชนิด สถานะ หลักฐาน สรุป"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const fa: TranslationSchema = {"footer":{"mode":{"browse":"مرور","insert":"درج","normal":"عادی","preview":"پیشنمایش","settings":"تنظیمات","files":"فایلها","graft":"Graft","history":"تاریخچه"},"context":{"settings":"تنظیمات","graft_empty":"برای بررسی یک فایل باز کنید","history_empty":"هنوز مدرک Echo وجود ندارد","history_count":"مدارک Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k حرکت","enter_change":"enter تغییر","f2_close":"f2 بستن","esc_close":"esc بستن","j_k_scroll":"j/k پیمایش","f3_source":"f3 منبع","f3_preview":"f3 پیشنمایش","text_input":"ورودی متن","esc_normal":"esc عادی","ctrl_s_save":"ctrl+s ذخیره","ctrl_t_theme":"ctrl+t پوسته","tab_focus":"tab تمرکز","tab_indent":"tab تورفتگی","ctrl_l_scene_picker":"ctrl+l صحنه","scene_picker":"انتخاب صحنه","ctrl_b_files":"ctrl+b فایلها","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h تاریخچه","enter_open":"enter باز کردن","backspace_up":"backspace بالا","ctrl_b_close":"ctrl+b بستن","enter_jump":"enter پرش","r_refresh":"r تازهسازی","ctrl_g_close":"ctrl+g بستن","ctrl_h_close":"ctrl+h بستن","i_insert":"i درج","o_open_line":"o باز کردن خط","cc_line":"cc خط","cw_word":"cw واژه","ce_word_end":"ce پایان واژه","c0_start":"c0 آغاز","c_end":"c$ پایان","dd_line":"dd خط","dw_word":"dw واژه","de_word_end":"de پایان واژه","d0_start":"d0 آغاز","d_end":"d$ پایان","yy_line":"yy خط","yw_word":"yw واژه","ye_word_end":"ye پایان واژه","y0_start":"y0 آغاز","y_end":"y$ پایان","gg_top":"gg بالا","esc_cancel":"esc لغو"},"evidence":{"head":"سر","tick":"تیک","anchor":"لنگر","coverage":"پوشش","complete":"کامل","partial":"جزئی","unavailable":"در دسترس نیست"}},"history":{"title":"تاریخچه Echo","empty":"هنوز مدرک Echo وجود ندارد","header":"# tick نوع وضعیت مدرک خلاصه"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const fa: TranslationSchema = {"footer":{"mode":{"browse":"مرور","insert":"درج","normal":"عادی","preview":"پیشنمایش","settings":"تنظیمات","files":"فایلها","graft":"Graft","history":"تاریخچه"},"context":{"settings":"تنظیمات","graft_empty":"برای بررسی یک فایل باز کنید","history_empty":"هنوز مدرک Echo وجود ندارد","history_count":"مدارک Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k حرکت","enter_change":"enter تغییر","f2_close":"f2 بستن","esc_close":"esc بستن","j_k_scroll":"j/k پیمایش","f3_source":"f3 منبع","f3_preview":"f3 پیشنمایش","text_input":"ورودی متن","esc_normal":"esc عادی","ctrl_s_save":"ctrl+s ذخیره","ctrl_t_theme":"ctrl+t پوسته","tab_focus":"tab تمرکز","tab_indent":"tab تورفتگی","ctrl_b_files":"ctrl+b فایلها","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h تاریخچه","enter_open":"enter باز کردن","backspace_up":"backspace بالا","ctrl_b_close":"ctrl+b بستن","enter_jump":"enter پرش","r_refresh":"r تازهسازی","ctrl_g_close":"ctrl+g بستن","ctrl_h_close":"ctrl+h بستن","i_insert":"i درج","o_open_line":"o باز کردن خط","cc_line":"cc خط","cw_word":"cw واژه","ce_word_end":"ce پایان واژه","c0_start":"c0 آغاز","c_end":"c$ پایان","dd_line":"dd خط","dw_word":"dw واژه","de_word_end":"de پایان واژه","d0_start":"d0 آغاز","d_end":"d$ پایان","yy_line":"yy خط","yw_word":"yw واژه","ye_word_end":"ye پایان واژه","y0_start":"y0 آغاز","y_end":"y$ پایان","gg_top":"gg بالا","esc_cancel":"esc لغو"},"evidence":{"head":"سر","tick":"تیک","anchor":"لنگر","coverage":"پوشش","complete":"کامل","partial":"جزئی","unavailable":"در دسترس نیست"}},"history":{"title":"تاریخچه Echo","empty":"هنوز مدرک Echo وجود ندارد","header":"# tick نوع وضعیت مدرک خلاصه"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const he: TranslationSchema = {"footer":{"mode":{"browse":"עיון","insert":"הוספה","normal":"רגיל","preview":"תצוגה","settings":"הגדרות","files":"קבצים","graft":"Graft","history":"היסטוריה"},"context":{"settings":"הגדרות","graft_empty":"פתח קובץ לבדיקה","history_empty":"עדיין אין ראיות Echo","history_count":"ראיות Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k הזז","enter_change":"enter שנה","f2_close":"f2 סגור","esc_close":"esc סגור","j_k_scroll":"j/k גלול","f3_source":"f3 מקור","f3_preview":"f3 תצוגה","text_input":"קלט טקסט","esc_normal":"esc רגיל","ctrl_s_save":"ctrl+s שמור","ctrl_t_theme":"ctrl+t ערכת נושא","tab_focus":"tab מיקוד","tab_indent":"tab הזחה","ctrl_l_scene_picker":"ctrl+l סצנה","scene_picker":"בחירת סצנה","ctrl_b_files":"ctrl+b קבצים","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h היסטוריה","enter_open":"enter פתח","backspace_up":"backspace למעלה","ctrl_b_close":"ctrl+b סגור","enter_jump":"enter קפוץ","r_refresh":"r רענן","ctrl_g_close":"ctrl+g סגור","ctrl_h_close":"ctrl+h סגור","i_insert":"i הוסף","o_open_line":"o פתח שורה","cc_line":"cc שורה","cw_word":"cw מילה","ce_word_end":"ce סוף מילה","c0_start":"c0 התחלה","c_end":"c$ סוף","dd_line":"dd שורה","dw_word":"dw מילה","de_word_end":"de סוף מילה","d0_start":"d0 התחלה","d_end":"d$ סוף","yy_line":"yy שורה","yw_word":"yw מילה","ye_word_end":"ye סוף מילה","y0_start":"y0 התחלה","y_end":"y$ סוף","gg_top":"gg למעלה","esc_cancel":"esc בטל"},"evidence":{"head":"ראש","tick":"טיק","anchor":"עוגן","coverage":"כיסוי","complete":"מלא","partial":"חלקי","unavailable":"לא זמין"}},"history":{"title":"היסטוריית Echo","empty":"עדיין אין ראיות Echo","header":"# tick סוג מצב ראיה סיכום"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
+export const he: TranslationSchema = {"footer":{"mode":{"browse":"עיון","insert":"הוספה","normal":"רגיל","preview":"תצוגה","settings":"הגדרות","files":"קבצים","graft":"Graft","history":"היסטוריה"},"context":{"settings":"הגדרות","graft_empty":"פתח קובץ לבדיקה","history_empty":"עדיין אין ראיות Echo","history_count":"ראיות Echo: {count}"},"command":{"invalid":"Command not recognized; type :help for help","hints":{"tab_accept":"tab accept","enter_run":"enter run","esc_cancel":"esc cancel"},"details":{"edit":"Open a file","write":"Write the current file","quit":"Quit jedit","wq":"Write and quit","ttd":"Observe a causal tick without moving canonical head","strand":"Create, switch, or list copy-on-write strands","braid":"View, preview, or admit braid candidates","why":"Explain the last meaningful command","help":"Show command help"}},"hints":{"j_k_move":"j/k הזז","enter_change":"enter שנה","f2_close":"f2 סגור","esc_close":"esc סגור","j_k_scroll":"j/k גלול","f3_source":"f3 מקור","f3_preview":"f3 תצוגה","text_input":"קלט טקסט","esc_normal":"esc רגיל","ctrl_s_save":"ctrl+s שמור","ctrl_t_theme":"ctrl+t ערכת נושא","tab_focus":"tab מיקוד","tab_indent":"tab הזחה","ctrl_b_files":"ctrl+b קבצים","ctrl_g_graft":"ctrl+g graft","ctrl_h_history":"ctrl+h היסטוריה","enter_open":"enter פתח","backspace_up":"backspace למעלה","ctrl_b_close":"ctrl+b סגור","enter_jump":"enter קפוץ","r_refresh":"r רענן","ctrl_g_close":"ctrl+g סגור","ctrl_h_close":"ctrl+h סגור","i_insert":"i הוסף","o_open_line":"o פתח שורה","cc_line":"cc שורה","cw_word":"cw מילה","ce_word_end":"ce סוף מילה","c0_start":"c0 התחלה","c_end":"c$ סוף","dd_line":"dd שורה","dw_word":"dw מילה","de_word_end":"de סוף מילה","d0_start":"d0 התחלה","d_end":"d$ סוף","yy_line":"yy שורה","yw_word":"yw מילה","ye_word_end":"ye סוף מילה","y0_start":"y0 התחלה","y_end":"y$ סוף","gg_top":"gg למעלה","esc_cancel":"esc בטל"},"evidence":{"head":"ראש","tick":"טיק","anchor":"עוגן","coverage":"כיסוי","complete":"מלא","partial":"חלקי","unavailable":"לא זמין"}},"history":{"title":"היסטוריית Echo","empty":"עדיין אין ראיות Echo","header":"# tick סוג מצב ראיה סיכום"},"startupFileModal":{"title":"Open file","current_directory":"Current directory","empty":"No files in this directory"},"settings":{"sections":{"appearance":"Appearance","editor":"Editor","runtime":"Runtime"},"rows":{"theme":{"label":"Theme","description":"Switch between installed data-driven themes."},"theme_mode":{"label":"Light/dark","description":"Switch the current theme to its light or dark companion."},"footer":{"label":"Footer","description":"Show mode, focus, and command hints at the bottom edge."},"markdown_preview":{"label":"Markdown preview","description":"Switch the active Markdown buffer between source and preview."},"diagnostics":{"label":"Diagnostics","description":"Inspect Graft, parser, and Colorful runtime wiring."},"line_numbers":{"label":"Line numbers","description":"Switch between absolute and cursor-relative editor line numbers."},"gutter_dimmed":{"label":"Dim gutter","description":"Use the theme dimmed gutter token set."},"causal_gutter_basis":{"label":"Causal markers","description":"Choose the causal basis used by gutter change markers."}},"values":{"on":"On","off":"Off","theme_mode_dark":"Dark","theme_mode_light":"Light","line_numbers_absolute":"Absolute","line_numbers_relative":"Relative","source":"Source","preview":"Preview","current":"Current","open":"Open","causal_gutter_last_save":"Last save","causal_gutter_import":"Import","causal_gutter_selected_checkpoint":"Selected checkpoint","causal_gutter_selected_tick":"Selected tick"},"toast":{"changed_title":"Settings changed"}},"worldline":{"title":"Worldlines","empty":"No worldlines yet","header":"kind name basis head delta conflict"},"why":{"range_obstructed_title":"Why range obstructed"}};
-export const me: TranslationSchema = {"footer":{"mode":{"browse":"esworb","insert":"tresni","normal":"lamron","preview":"weiverp","settings":"sgnittes","files":"selif","graft":"tfarg","history":"yrotsih"},"context":{"settings":"sgnittes","graft_empty":"ti tcepsni ot elif a nepo","history_empty":"tey ecnedive ohcE on","history_count":"{count} :ecnedive ohcE"},"command":{"invalid":"pleh rof pleh: epyt ;dezingocer ton dnammoC","hints":{"tab_accept":"tpecca bat","enter_run":"nur retne","esc_cancel":"lecnac cse"},"details":{"edit":"elif a nepO","write":"elif tnerruc eht etirW","quit":"tidej tiuQ","wq":"tiuq dna etirW","ttd":"daeh lacinonac gnivom tuohtiw kcit lasuac a evresbO","strand":"sdnarts etirw-no-ypoc tsil ro ,hctiws ,etaerC","braid":"setadidnac diarb timda ro ,weiverp ,weiV","why":"dnammoc lufgninaem tsal eht nialpxE","help":"pleh dnammoc wohS"}},"hints":{"j_k_move":"evom k/j","enter_change":"egnahc retne","f2_close":"esolc 2f","esc_close":"esolc cse","j_k_scroll":"llorcs k/j","f3_source":"ecruos 3f","f3_preview":"weiverp 3f","text_input":"tupni txet","esc_normal":"lamron cse","ctrl_s_save":"evas s+lrtc","ctrl_t_theme":"emeht t+lrtc","tab_focus":"sucof bat","tab_indent":"tnedni bat","ctrl_l_scene_picker":"rekcip enecs l+lrtc","scene_picker":"rekcip enecs","ctrl_b_files":"selif b+lrtc","ctrl_g_graft":"tfarg g+lrtc","ctrl_h_history":"yrotsih h+lrtc","enter_open":"nepo retne","backspace_up":"pu ecapskcab","ctrl_b_close":"esolc b+lrtc","enter_jump":"pmuj retne","r_refresh":"hserfer r","ctrl_g_close":"esolc g+lrtc","ctrl_h_close":"esolc h+lrtc","i_insert":"tresni i","o_open_line":"enil nepo o","cc_line":"enil cc","cw_word":"drow wc","ce_word_end":"dne-drow ec","c0_start":"trats 0c","c_end":"dne $c","dd_line":"enil dd","dw_word":"drow wd","de_word_end":"dne-drow ed","d0_start":"trats 0d","d_end":"dne $d","yy_line":"enil yy","yw_word":"drow wy","ye_word_end":"dne-drow ey","y0_start":"trats 0y","y_end":"dne $y","gg_top":"pot gg","esc_cancel":"lecnac cse"},"evidence":{"head":"daeh","tick":"kcit","anchor":"rohcna","coverage":"egarevoc","complete":"etelpmoc","partial":"laitrap","unavailable":"elbaliavanu"}},"history":{"title":"yrotsiH ohcE","empty":"tey ecnedive ohcE oN","header":"yrammus ecnedive sutats dnik kcit #"},"startupFileModal":{"title":"elif nepO","current_directory":"yrotcerid tnerruC","empty":"yrotcerid siht ni selif oN"},"settings":{"sections":{"appearance":"ecnaraeppA","editor":"rotidE","runtime":"emitnuR"},"rows":{"theme":{"label":"emehT","description":".semeht nevird-atad dellatsni neewteb hctiwS"},"theme_mode":{"label":"krad/thgiL","description":".noinapmoc krad ro thgil sti ot emeht tnerruc eht hctiwS"},"footer":{"label":"retooF","description":".egde mottob eht ta stnih dnammoc dna ,sucof ,edom wohS"},"markdown_preview":{"label":"weiverp nwodkraM","description":".weiverp dna ecruos neewteb reffub nwodkraM evitca eht hctiwS"},"diagnostics":{"label":"scitsongaiD","description":".gniriw emitnur lufroloC dna ,resrap ,tfarG tcepsnI"},"line_numbers":{"label":"srebmun eniL","description":".srebmun enil rotide evitaler-rosruc dna etulosba neewteb hctiwS"},"gutter_dimmed":{"label":"rettug miD","description":".tes nekot rettug demmid emeht eht esU"},"causal_gutter_basis":{"label":"srekram lasuaC","description":".srekram egnahc rettug yb desu sisab lasuac eht esoohC"}},"values":{"on":"nO","off":"ffO","theme_mode_dark":"kraD","theme_mode_light":"thgiL","line_numbers_absolute":"etulosbA","line_numbers_relative":"evitaleR","source":"ecruoS","preview":"weiverP","current":"tnerruC","open":"nepO","causal_gutter_last_save":"evas tsaL","causal_gutter_import":"tropmI","causal_gutter_selected_checkpoint":"tniopkcehc detceleS","causal_gutter_selected_tick":"kcit detceleS"},"toast":{"changed_title":"degnahc sgnitteS"}},"worldline":{"title":"senildlroW","empty":"tey senildlrow oN","header":"tcilfnoc atled daeh sisab eman dnik"},"why":{"range_obstructed_title":"detcurtsbo egnar yhW"}};
+export const me: TranslationSchema = {"footer":{"mode":{"browse":"esworb","insert":"tresni","normal":"lamron","preview":"weiverp","settings":"sgnittes","files":"selif","graft":"tfarg","history":"yrotsih"},"context":{"settings":"sgnittes","graft_empty":"ti tcepsni ot elif a nepo","history_empty":"tey ecnedive ohcE on","history_count":"{count} :ecnedive ohcE"},"command":{"invalid":"pleh rof pleh: epyt ;dezingocer ton dnammoC","hints":{"tab_accept":"tpecca bat","enter_run":"nur retne","esc_cancel":"lecnac cse"},"details":{"edit":"elif a nepO","write":"elif tnerruc eht etirW","quit":"tidej tiuQ","wq":"tiuq dna etirW","ttd":"daeh lacinonac gnivom tuohtiw kcit lasuac a evresbO","strand":"sdnarts etirw-no-ypoc tsil ro ,hctiws ,etaerC","braid":"setadidnac diarb timda ro ,weiverp ,weiV","why":"dnammoc lufgninaem tsal eht nialpxE","help":"pleh dnammoc wohS"}},"hints":{"j_k_move":"evom k/j","enter_change":"egnahc retne","f2_close":"esolc 2f","esc_close":"esolc cse","j_k_scroll":"llorcs k/j","f3_source":"ecruos 3f","f3_preview":"weiverp 3f","text_input":"tupni txet","esc_normal":"lamron cse","ctrl_s_save":"evas s+lrtc","ctrl_t_theme":"emeht t+lrtc","tab_focus":"sucof bat","tab_indent":"tnedni bat","ctrl_b_files":"selif b+lrtc","ctrl_g_graft":"tfarg g+lrtc","ctrl_h_history":"yrotsih h+lrtc","enter_open":"nepo retne","backspace_up":"pu ecapskcab","ctrl_b_close":"esolc b+lrtc","enter_jump":"pmuj retne","r_refresh":"hserfer r","ctrl_g_close":"esolc g+lrtc","ctrl_h_close":"esolc h+lrtc","i_insert":"tresni i","o_open_line":"enil nepo o","cc_line":"enil cc","cw_word":"drow wc","ce_word_end":"dne-drow ec","c0_start":"trats 0c","c_end":"dne $c","dd_line":"enil dd","dw_word":"drow wd","de_word_end":"dne-drow ed","d0_start":"trats 0d","d_end":"dne $d","yy_line":"enil yy","yw_word":"drow wy","ye_word_end":"dne-drow ey","y0_start":"trats 0y","y_end":"dne $y","gg_top":"pot gg","esc_cancel":"lecnac cse"},"evidence":{"head":"daeh","tick":"kcit","anchor":"rohcna","coverage":"egarevoc","complete":"etelpmoc","partial":"laitrap","unavailable":"elbaliavanu"}},"history":{"title":"yrotsiH ohcE","empty":"tey ecnedive ohcE oN","header":"yrammus ecnedive sutats dnik kcit #"},"startupFileModal":{"title":"elif nepO","current_directory":"yrotcerid tnerruC","empty":"yrotcerid siht ni selif oN"},"settings":{"sections":{"appearance":"ecnaraeppA","editor":"rotidE","runtime":"emitnuR"},"rows":{"theme":{"label":"emehT","description":".semeht nevird-atad dellatsni neewteb hctiwS"},"theme_mode":{"label":"krad/thgiL","description":".noinapmoc krad ro thgil sti ot emeht tnerruc eht hctiwS"},"footer":{"label":"retooF","description":".egde mottob eht ta stnih dnammoc dna ,sucof ,edom wohS"},"markdown_preview":{"label":"weiverp nwodkraM","description":".weiverp dna ecruos neewteb reffub nwodkraM evitca eht hctiwS"},"diagnostics":{"label":"scitsongaiD","description":".gniriw emitnur lufroloC dna ,resrap ,tfarG tcepsnI"},"line_numbers":{"label":"srebmun eniL","description":".srebmun enil rotide evitaler-rosruc dna etulosba neewteb hctiwS"},"gutter_dimmed":{"label":"rettug miD","description":".tes nekot rettug demmid emeht eht esU"},"causal_gutter_basis":{"label":"srekram lasuaC","description":".srekram egnahc rettug yb desu sisab lasuac eht esoohC"}},"values":{"on":"nO","off":"ffO","theme_mode_dark":"kraD","theme_mode_light":"thgiL","line_numbers_absolute":"etulosbA","line_numbers_relative":"evitaleR","source":"ecruoS","preview":"weiverP","current":"tnerruC","open":"nepO","causal_gutter_last_save":"evas tsaL","causal_gutter_import":"tropmI","causal_gutter_selected_checkpoint":"tniopkcehc detceleS","causal_gutter_selected_tick":"kcit detceleS"},"toast":{"changed_title":"degnahc sgnitteS"}},"worldline":{"title":"senildlroW","empty":"tey senildlrow oN","header":"tcilfnoc atled daeh sisab eman dnik"},"why":{"range_obstructed_title":"detcurtsbo egnar yhW"}};
export const locales = ["en", "fr", "it", "de", "es", "ko", "ja", "zh-Hans", "pt-BR", "ru", "ar", "id", "tr", "hi", "nl", "pl", "vi", "th", "fa", "he", "me"] as const;
diff --git a/src/ui/help.ts b/src/ui/help.ts
index 8290a3b6..0cdb01d3 100644
--- a/src/ui/help.ts
+++ b/src/ui/help.ts
@@ -2,7 +2,6 @@ import type { Overlay } from '@flyingrobots/bijou-tui';
import {
JEDIT_MARKDOWN_PREVIEW_TOGGLE_LABEL,
- JEDIT_SCENE_PICKER_TOGGLE_LABEL,
JEDIT_SETTINGS_TOGGLE_LABEL,
} from '../app/keybindings.js';
import { renderHelpOverlay } from './feedback.js';
@@ -36,7 +35,6 @@ const HELP_DIALOG_BODY = [
' enter jump to symbol',
'',
'general',
- ` ${JEDIT_SCENE_PICKER_TOGGLE_LABEL} open scene picker (title screen only)`,
' ? helper',
' q quit',
].join('\n');
diff --git a/src/ui/scene-picker-drawer.ts b/src/ui/scene-picker-drawer.ts
deleted file mode 100644
index 36b977dd..00000000
--- a/src/ui/scene-picker-drawer.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import { createSurface, stringToSurface, type Surface } from '@flyingrobots/bijou';
-import { JEDIT_SCENE_PICKER_TOGGLE_LABEL } from '../app/keybindings.js';
-import { JEDIT_MARKDOWN_TOKEN, type JeditStyleToken, type JeditTheme } from './jedit-theme.js';
-import { fitLine } from './fit-line.js';
-
-const SCENE_PICKER_DRAWER_MIN_WIDTH = 28;
-const SCENE_PICKER_DRAWER_MAX_WIDTH = 42;
-const SCENE_PICKER_DRAWER_WIDTH_RATIO = 0.3;
-const SCENE_PICKER_HEADER_ROW = 1;
-const SCENE_PICKER_HINT_ROW = 2;
-const SCENE_PICKER_FIRST_ROW = 4;
-const SCENE_PICKER_LEFT_PAD = 2;
-const SCENE_PICKER_ROW_HEIGHT = 1;
-const SCENE_PICKER_SELECTED_MARK = '›';
-const SCENE_PICKER_UNSELECTED_MARK = ' ';
-const SCENE_PICKER_CLOSE_HINT = `${JEDIT_SCENE_PICKER_TOGGLE_LABEL.toUpperCase()}/Esc close, Enter load`;
-const SCENE_PICKER_TITLE = 'Load Scene';
-
-export interface RenderScenePickerDrawerOptions {
- readonly scenes: readonly string[];
- readonly selectedIndex: number;
- readonly theme: JeditTheme;
- readonly width: number;
- readonly height: number;
-}
-
-export function resolveScenePickerDrawerWidth(columns: number): number {
- const boundedColumns = Math.max(SCENE_PICKER_DRAWER_MIN_WIDTH, columns);
- return Math.min(
- Math.max(SCENE_PICKER_DRAWER_MIN_WIDTH, Math.floor(boundedColumns * SCENE_PICKER_DRAWER_WIDTH_RATIO)),
- Math.max(SCENE_PICKER_DRAWER_MIN_WIDTH, boundedColumns - SCENE_PICKER_LEFT_PAD),
- SCENE_PICKER_DRAWER_MAX_WIDTH,
- );
-}
-
-export function renderScenePickerDrawer(options: RenderScenePickerDrawerOptions): Surface {
- const surface = createSurface(options.width, options.height);
- fillSurface(surface, options.theme.surface.drawer);
- paintText(surface, SCENE_PICKER_TITLE, SCENE_PICKER_LEFT_PAD, SCENE_PICKER_HEADER_ROW, scenePickerTitleToken(options));
- paintText(surface, SCENE_PICKER_CLOSE_HINT, SCENE_PICKER_LEFT_PAD, SCENE_PICKER_HINT_ROW, options.theme.surface.drawer);
-
- let y = SCENE_PICKER_FIRST_ROW;
- for (let index = 0; index < options.scenes.length && y < options.height; index += 1) {
- const scene = options.scenes[index];
- if (scene == null) continue;
- const selected = index === options.selectedIndex;
- const label = fitLine(scenePickerRowLabel({ selected, scene }), Math.max(1, options.width - SCENE_PICKER_LEFT_PAD));
- const token = selected ? options.theme.cursor.normal : options.theme.surface.drawer;
- paintText(surface, label, SCENE_PICKER_LEFT_PAD, y, token);
- y += SCENE_PICKER_ROW_HEIGHT;
- }
- return surface;
-}
-
-function scenePickerTitleToken(options: RenderScenePickerDrawerOptions): JeditStyleToken {
- return options.theme.markdown.get(JEDIT_MARKDOWN_TOKEN.Heading) ?? options.theme.surface.drawer;
-}
-
-function scenePickerRowLabel(options: { readonly selected: boolean; readonly scene: string }): string {
- return `${options.selected ? SCENE_PICKER_SELECTED_MARK : SCENE_PICKER_UNSELECTED_MARK} ${options.scene}`;
-}
-
-function paintText(surface: Surface, text: string, x: number, y: number, token: JeditStyleToken): void {
- if (y < 0 || y >= surface.height || x >= surface.width) {
- return;
- }
- const width = Math.max(1, surface.width - x);
- const line = stringToSurface(fitLine(text, width), width, 1);
- applyToken(line, token);
- surface.blit(line, x, y);
-}
-
-function fillSurface(surface: Surface, token: JeditStyleToken): void {
- surface.fill({
- char: ' ',
- fg: token.fg,
- bg: token.bg,
- fgRGB: token.fgRGB,
- bgRGB: token.bgRGB,
- empty: false,
- });
-}
-
-function applyToken(surface: Surface, token: JeditStyleToken): void {
- for (let y = 0; y < surface.height; y += 1) {
- for (let x = 0; x < surface.width; x += 1) {
- const cell = surface.get(x, y);
- surface.set(x, y, {
- ...cell,
- fg: token.fg ?? cell.fg,
- bg: token.bg ?? cell.bg,
- fgRGB: token.fgRGB ?? cell.fgRGB,
- bgRGB: token.bgRGB ?? cell.bgRGB,
- modifiers: token.modifiers == null ? cell.modifiers : [...token.modifiers],
- empty: false,
- });
- }
- }
-}
diff --git a/src/ui/workspace-chrome.ts b/src/ui/workspace-chrome.ts
index 4a2b1ce3..2c3307a2 100644
--- a/src/ui/workspace-chrome.ts
+++ b/src/ui/workspace-chrome.ts
@@ -52,7 +52,6 @@ const FooterHintKeys = Object.freeze({
CtrlSSave: 'ctrl_s_save',
TabFocus: 'tab_focus',
TabIndent: 'tab_indent',
- CtrlLScenePicker: 'ctrl_l_scene_picker',
CtrlBFiles: 'ctrl_b_files',
CtrlGGraft: 'ctrl_g_graft',
EnterOpen: 'enter_open',
@@ -237,7 +236,6 @@ function footerDetail(state: WorkspaceFooterState): string {
return activeFooterDetail(state, t)
?? footerHints([
- scenePickerHint(t),
focusHint(state, t),
t(FooterHintKeys.CtrlBFiles),
t(FooterHintKeys.CtrlGGraft),
@@ -278,11 +276,6 @@ function footerHintTranslator(state: WorkspaceFooterState): FooterHintTranslator
return (key: string) => state.i18n.t(`footer.hints.${key}`);
}
-function scenePickerHint(t: FooterHintTranslator): string {
- return t(FooterHintKeys.CtrlLScenePicker);
-}
-
-
function drawerFooterDetail(state: WorkspaceFooterState, kind: DrawerKind, t: FooterHintTranslator): string {
if (kind === DrawerKinds.Files) {
return footerHints(fileDrawerFooterHints(state, t));
diff --git a/translations.json b/translations.json
index 598abf31..d63a553f 100644
--- a/translations.json
+++ b/translations.json
@@ -54,8 +54,6 @@
"ctrl_t_theme": "ctrl+t theme",
"tab_focus": "tab focus",
"tab_indent": "tab indent",
- "ctrl_l_scene_picker": "ctrl+l scene picker",
- "scene_picker": "scene picker",
"ctrl_b_files": "ctrl+b files",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h history",
@@ -230,8 +228,6 @@
"ctrl_t_theme": "ctrl+t thème",
"tab_focus": "tab focus",
"tab_indent": "tab retrait",
- "ctrl_l_scene_picker": "ctrl+l scène",
- "scene_picker": "sélecteur de scène",
"ctrl_b_files": "ctrl+b fichiers",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h historique",
@@ -406,8 +402,6 @@
"ctrl_t_theme": "ctrl+t tema",
"tab_focus": "tab focus",
"tab_indent": "tab rientra",
- "ctrl_l_scene_picker": "ctrl+l scena",
- "scene_picker": "selettore scena",
"ctrl_b_files": "ctrl+b file",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h cronologia",
@@ -582,8 +576,6 @@
"ctrl_t_theme": "ctrl+t theme",
"tab_focus": "tab fokus",
"tab_indent": "tab einrücken",
- "ctrl_l_scene_picker": "ctrl+l szene",
- "scene_picker": "szenenauswahl",
"ctrl_b_files": "ctrl+b dateien",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h verlauf",
@@ -758,8 +750,6 @@
"ctrl_t_theme": "ctrl+t tema",
"tab_focus": "tab foco",
"tab_indent": "tab sangría",
- "ctrl_l_scene_picker": "ctrl+l escena",
- "scene_picker": "selector de escena",
"ctrl_b_files": "ctrl+b archivos",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h historial",
@@ -934,8 +924,6 @@
"ctrl_t_theme": "ctrl+t 테마",
"tab_focus": "tab 포커스",
"tab_indent": "tab 들여쓰기",
- "ctrl_l_scene_picker": "ctrl+l 장면",
- "scene_picker": "장면 선택",
"ctrl_b_files": "ctrl+b 파일",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h 기록",
@@ -1110,8 +1098,6 @@
"ctrl_t_theme": "ctrl+t テーマ",
"tab_focus": "tab フォーカス",
"tab_indent": "tab インデント",
- "ctrl_l_scene_picker": "ctrl+l シーン",
- "scene_picker": "シーン選択",
"ctrl_b_files": "ctrl+b ファイル",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h 履歴",
@@ -1286,8 +1272,6 @@
"ctrl_t_theme": "ctrl+t 主题",
"tab_focus": "tab 焦点",
"tab_indent": "tab 缩进",
- "ctrl_l_scene_picker": "ctrl+l 场景",
- "scene_picker": "场景选择",
"ctrl_b_files": "ctrl+b 文件",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h 历史",
@@ -1462,8 +1446,6 @@
"ctrl_t_theme": "ctrl+t tema",
"tab_focus": "tab foco",
"tab_indent": "tab recuo",
- "ctrl_l_scene_picker": "ctrl+l cena",
- "scene_picker": "seletor de cena",
"ctrl_b_files": "ctrl+b arquivos",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h histórico",
@@ -1638,8 +1620,6 @@
"ctrl_t_theme": "ctrl+t тема",
"tab_focus": "tab фокус",
"tab_indent": "tab отступ",
- "ctrl_l_scene_picker": "ctrl+l сцена",
- "scene_picker": "выбор сцены",
"ctrl_b_files": "ctrl+b файлы",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h история",
@@ -1814,8 +1794,6 @@
"ctrl_t_theme": "ctrl+t سمة",
"tab_focus": "tab تركيز",
"tab_indent": "tab إزاحة",
- "ctrl_l_scene_picker": "ctrl+l مشهد",
- "scene_picker": "اختيار مشهد",
"ctrl_b_files": "ctrl+b ملفات",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h سجل",
@@ -1990,8 +1968,6 @@
"ctrl_t_theme": "ctrl+t tema",
"tab_focus": "tab fokus",
"tab_indent": "tab inden",
- "ctrl_l_scene_picker": "ctrl+l adegan",
- "scene_picker": "pilih adegan",
"ctrl_b_files": "ctrl+b berkas",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h riwayat",
@@ -2166,8 +2142,6 @@
"ctrl_t_theme": "ctrl+t tema",
"tab_focus": "tab odak",
"tab_indent": "tab girinti",
- "ctrl_l_scene_picker": "ctrl+l sahne",
- "scene_picker": "sahne seçici",
"ctrl_b_files": "ctrl+b dosyalar",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h geçmiş",
@@ -2342,8 +2316,6 @@
"ctrl_t_theme": "ctrl+t थीम",
"tab_focus": "tab फोकस",
"tab_indent": "tab इंडेंट",
- "ctrl_l_scene_picker": "ctrl+l दृश्य",
- "scene_picker": "दृश्य चयन",
"ctrl_b_files": "ctrl+b फाइलें",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h इतिहास",
@@ -2518,8 +2490,6 @@
"ctrl_t_theme": "ctrl+t thema",
"tab_focus": "tab focus",
"tab_indent": "tab inspringen",
- "ctrl_l_scene_picker": "ctrl+l scène",
- "scene_picker": "scènekiezer",
"ctrl_b_files": "ctrl+b bestanden",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h geschiedenis",
@@ -2694,8 +2664,6 @@
"ctrl_t_theme": "ctrl+t motyw",
"tab_focus": "tab fokus",
"tab_indent": "tab wcięcie",
- "ctrl_l_scene_picker": "ctrl+l scena",
- "scene_picker": "wybór sceny",
"ctrl_b_files": "ctrl+b pliki",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h historia",
@@ -2870,8 +2838,6 @@
"ctrl_t_theme": "ctrl+t giao diện",
"tab_focus": "tab tiêu điểm",
"tab_indent": "tab thụt lề",
- "ctrl_l_scene_picker": "ctrl+l cảnh",
- "scene_picker": "chọn cảnh",
"ctrl_b_files": "ctrl+b tệp",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h lịch sử",
@@ -3046,8 +3012,6 @@
"ctrl_t_theme": "ctrl+t ธีม",
"tab_focus": "tab โฟกัส",
"tab_indent": "tab เยื้อง",
- "ctrl_l_scene_picker": "ctrl+l ฉาก",
- "scene_picker": "เลือกฉาก",
"ctrl_b_files": "ctrl+b ไฟล์",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h ประวัติ",
@@ -3222,8 +3186,6 @@
"ctrl_t_theme": "ctrl+t پوسته",
"tab_focus": "tab تمرکز",
"tab_indent": "tab تورفتگی",
- "ctrl_l_scene_picker": "ctrl+l صحنه",
- "scene_picker": "انتخاب صحنه",
"ctrl_b_files": "ctrl+b فایلها",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h تاریخچه",
@@ -3398,8 +3360,6 @@
"ctrl_t_theme": "ctrl+t ערכת נושא",
"tab_focus": "tab מיקוד",
"tab_indent": "tab הזחה",
- "ctrl_l_scene_picker": "ctrl+l סצנה",
- "scene_picker": "בחירת סצנה",
"ctrl_b_files": "ctrl+b קבצים",
"ctrl_g_graft": "ctrl+g graft",
"ctrl_h_history": "ctrl+h היסטוריה",