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. - -Title screen layout across wide, narrow, and xs terminal profiles. +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 היסטוריה",