@crouton-kit/crouter 0.3.18 → 0.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-root.js +6 -0
- package/dist/builtin-memory/crouter-development/marketplaces.md +164 -0
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +58 -0
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +60 -0
- package/dist/builtin-memory/crouter-development/personas.md +117 -0
- package/dist/builtin-memory/crouter-development/plugins.md +163 -0
- package/dist/builtin-memory/design.md +60 -0
- package/dist/builtin-memory/development.md +116 -0
- package/dist/builtin-memory/planning.md +66 -0
- package/dist/builtin-memory/spec.md +99 -0
- package/dist/builtin-personas/design/PERSONA.md +1 -0
- package/dist/builtin-personas/design/orchestrator.md +2 -1
- package/dist/builtin-personas/developer/PERSONA.md +1 -0
- package/dist/builtin-personas/developer/orchestrator.md +2 -1
- package/dist/builtin-personas/explore/PERSONA.md +1 -0
- package/dist/builtin-personas/explore/orchestrator.md +4 -0
- package/dist/builtin-personas/general/PERSONA.md +1 -0
- package/dist/builtin-personas/general/orchestrator.md +4 -0
- package/dist/builtin-personas/lifecycle/resident.md +1 -1
- package/dist/builtin-personas/lifecycle/terminal.md +5 -2
- package/dist/builtin-personas/orchestration-kernel.md +15 -24
- package/dist/builtin-personas/plan/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/orchestrator.md +2 -1
- package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -0
- package/dist/builtin-personas/review/PERSONA.md +1 -0
- package/dist/builtin-personas/review/orchestrator.md +4 -0
- package/dist/builtin-personas/runtime-base.md +6 -3
- package/dist/builtin-personas/spec/PERSONA.md +1 -0
- package/dist/builtin-personas/spec/orchestrator.md +2 -1
- package/dist/builtin-personas/waiting.md +8 -0
- package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +1 -1
- package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +2 -2
- package/dist/builtin-views/_lib/states.mjs +161 -0
- package/dist/builtin-views/canvas/client.mjs +303 -0
- package/dist/builtin-views/canvas/view.mjs +576 -0
- package/dist/builtin-views/git-pr/client.mjs +440 -0
- package/dist/builtin-views/git-pr/view.mjs +675 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +177 -0
- package/dist/builtin-views/inbox/sources/gmail.mjs +965 -0
- package/dist/builtin-views/inbox/sources/linkedin.mjs +427 -0
- package/dist/builtin-views/inbox/view.mjs +889 -0
- package/dist/builtin-views/linkedin/client.mjs +610 -0
- package/dist/builtin-views/linkedin/view.mjs +1171 -0
- package/dist/clients/attach/attach-cmd.d.ts +2 -0
- package/dist/clients/attach/attach-cmd.js +354 -0
- package/dist/clients/attach/chat-view.d.ts +77 -0
- package/dist/clients/attach/chat-view.js +450 -0
- package/dist/clients/attach/clipboard-image.d.ts +16 -0
- package/dist/clients/attach/clipboard-image.js +113 -0
- package/dist/clients/attach/config-load.d.ts +31 -0
- package/dist/clients/attach/config-load.js +113 -0
- package/dist/clients/attach/extension-dialogs.d.ts +29 -0
- package/dist/clients/attach/extension-dialogs.js +101 -0
- package/dist/clients/attach/input-controller.d.ts +54 -0
- package/dist/clients/attach/input-controller.js +204 -0
- package/dist/clients/attach/slash-commands.d.ts +36 -0
- package/dist/clients/attach/slash-commands.js +200 -0
- package/dist/clients/attach/view-socket.d.ts +48 -0
- package/dist/clients/attach/view-socket.js +126 -0
- package/dist/commands/attention.js +3 -3
- package/dist/commands/canvas-prune.js +1 -1
- package/dist/commands/daemon.js +3 -2
- package/dist/commands/human/prompts.js +1 -1
- package/dist/commands/human/queue.js +54 -8
- package/dist/commands/human/shared.d.ts +2 -0
- package/dist/commands/memory/find.d.ts +1 -0
- package/dist/commands/memory/find.js +180 -0
- package/dist/commands/memory/lint.d.ts +1 -0
- package/dist/commands/memory/lint.js +140 -0
- package/dist/commands/memory/list.d.ts +1 -0
- package/dist/commands/memory/list.js +79 -0
- package/dist/commands/memory/read.js +103 -0
- package/dist/commands/memory/shared.d.ts +30 -0
- package/dist/commands/memory/shared.js +122 -0
- package/dist/commands/memory/write.d.ts +1 -0
- package/dist/commands/memory/write.js +85 -0
- package/dist/commands/memory.d.ts +2 -0
- package/dist/commands/memory.js +27 -0
- package/dist/commands/node.d.ts +3 -2
- package/dist/commands/node.js +610 -61
- package/dist/commands/pkg/market-manage.js +1 -1
- package/dist/commands/push.js +6 -6
- package/dist/commands/revive.js +1 -1
- package/dist/commands/skill/author.js +1 -1
- package/dist/commands/skill/shared.d.ts +1 -8
- package/dist/commands/skill/shared.js +2 -55
- package/dist/commands/skill.js +9 -14
- package/dist/commands/sys/doctor.js +1 -1
- package/dist/commands/sys/update.js +1 -1
- package/dist/commands/view-cycle.d.ts +2 -0
- package/dist/commands/view-cycle.js +125 -0
- package/dist/commands/view-list.d.ts +2 -0
- package/dist/commands/view-list.js +66 -0
- package/dist/commands/view-new.d.ts +2 -0
- package/dist/commands/view-new.js +70 -0
- package/dist/commands/view-pick.d.ts +2 -0
- package/dist/commands/view-pick.js +119 -0
- package/dist/commands/view-run.d.ts +2 -0
- package/dist/commands/view-run.js +191 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +29 -0
- package/dist/core/__tests__/broker-attach-limits.test.d.ts +1 -0
- package/dist/core/__tests__/broker-attach-limits.test.js +157 -0
- package/dist/core/__tests__/broker-attach-stream.test.d.ts +1 -0
- package/dist/core/__tests__/broker-attach-stream.test.js +125 -0
- package/dist/core/__tests__/broker-crash-teardown.test.d.ts +1 -0
- package/dist/core/__tests__/broker-crash-teardown.test.js +116 -0
- package/dist/core/__tests__/broker-dialogs.test.d.ts +1 -0
- package/dist/core/__tests__/broker-dialogs.test.js +126 -0
- package/dist/core/__tests__/broker-dormant-wake.test.d.ts +1 -0
- package/dist/core/__tests__/broker-dormant-wake.test.js +51 -0
- package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
- package/dist/core/__tests__/broker-lifecycle.test.js +87 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.d.ts +1 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +166 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.d.ts +1 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +102 -0
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +2 -36
- package/dist/core/__tests__/cascade-close.test.js +12 -2
- package/dist/core/__tests__/child-death-wake.test.d.ts +1 -0
- package/dist/core/__tests__/child-death-wake.test.js +245 -0
- package/dist/core/__tests__/context-intro.test.js +76 -62
- package/dist/core/__tests__/daemon-boot.test.js +14 -5
- package/dist/core/__tests__/daemon-liveness.test.js +34 -9
- package/dist/core/__tests__/detach-focus.test.d.ts +1 -0
- package/dist/core/__tests__/detach-focus.test.js +206 -0
- package/dist/core/__tests__/draw-style.test.d.ts +1 -0
- package/dist/core/__tests__/draw-style.test.js +258 -0
- package/dist/core/__tests__/fixtures/c3-custom-provider-ext.d.ts +2 -0
- package/dist/core/__tests__/fixtures/c3-custom-provider-ext.js +18 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +138 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +614 -0
- package/dist/core/__tests__/fixtures/fake-pi-host.js +1 -0
- package/dist/core/__tests__/flagship-lifecycle.test.js +7 -1
- package/dist/core/__tests__/frame-decoder-perf.test.d.ts +1 -0
- package/dist/core/__tests__/frame-decoder-perf.test.js +198 -0
- package/dist/core/__tests__/helpers/broker-clients.d.ts +43 -0
- package/dist/core/__tests__/helpers/broker-clients.js +178 -0
- package/dist/core/__tests__/helpers/harness.d.ts +9 -0
- package/dist/core/__tests__/helpers/harness.js +111 -1
- package/dist/core/__tests__/home-session.test.js +41 -8
- package/dist/core/__tests__/human-new-window-regression.test.d.ts +1 -0
- package/dist/core/__tests__/human-new-window-regression.test.js +101 -0
- package/dist/core/__tests__/kickoff.test.js +37 -3
- package/dist/core/__tests__/live-mutation-verbs.test.d.ts +1 -0
- package/dist/core/__tests__/live-mutation-verbs.test.js +175 -0
- package/dist/core/__tests__/live-mutation.test.js +6 -132
- package/dist/core/__tests__/memory.test.js +23 -115
- package/dist/core/__tests__/placement-focus.test.js +5 -0
- package/dist/core/__tests__/placement-teardown.test.js +54 -11
- package/dist/core/__tests__/relaunch.test.js +4 -3
- package/dist/core/__tests__/review-render-pane-regression.test.d.ts +1 -0
- package/dist/core/__tests__/review-render-pane-regression.test.js +133 -0
- package/dist/core/__tests__/spawn-root.test.js +10 -0
- package/dist/core/__tests__/spike-harness.test.js +1 -0
- package/dist/core/__tests__/wake-bearings.test.d.ts +1 -0
- package/dist/core/__tests__/wake-bearings.test.js +156 -0
- package/dist/core/__tests__/wake-origin.test.d.ts +1 -0
- package/dist/core/__tests__/wake-origin.test.js +110 -0
- package/dist/core/bootstrap.js +1 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.js +24 -2
- package/dist/core/canvas/browse/model.d.ts +38 -2
- package/dist/core/canvas/browse/model.js +134 -10
- package/dist/core/canvas/browse/render.d.ts +6 -12
- package/dist/core/canvas/browse/render.js +72 -104
- package/dist/core/canvas/canvas.js +16 -23
- package/dist/core/canvas/db.js +46 -0
- package/dist/core/canvas/index.d.ts +1 -0
- package/dist/core/canvas/index.js +1 -0
- package/dist/core/canvas/pid.d.ts +4 -0
- package/dist/core/canvas/pid.js +23 -0
- package/dist/core/canvas/render.d.ts +11 -2
- package/dist/core/canvas/render.js +69 -0
- package/dist/core/canvas/types.d.ts +91 -3
- package/dist/core/canvas/wakeups.d.ts +76 -0
- package/dist/core/canvas/wakeups.js +185 -0
- package/dist/core/config.js +4 -1
- package/dist/core/frontmatter.js +37 -124
- package/dist/core/memory-resolver.d.ts +49 -0
- package/dist/core/memory-resolver.js +141 -0
- package/dist/core/personas/loader.d.ts +7 -0
- package/dist/core/personas/loader.js +36 -5
- package/dist/core/personas/resolve.js +13 -3
- package/dist/core/predicate.d.ts +63 -0
- package/dist/core/predicate.js +189 -0
- package/dist/core/resolver.js +26 -5
- package/dist/core/runtime/bearings.d.ts +53 -12
- package/dist/core/runtime/bearings.js +132 -59
- package/dist/core/runtime/broker-cli.d.ts +1 -0
- package/dist/core/runtime/broker-cli.js +46 -0
- package/dist/core/runtime/broker-protocol.d.ts +332 -0
- package/dist/core/runtime/broker-protocol.js +153 -0
- package/dist/core/runtime/broker-sdk.d.ts +48 -0
- package/dist/core/runtime/broker-sdk.js +72 -0
- package/dist/core/runtime/broker.d.ts +55 -0
- package/dist/core/runtime/broker.js +1128 -0
- package/dist/core/runtime/close.js +35 -6
- package/dist/core/runtime/host.d.ts +53 -0
- package/dist/core/runtime/host.js +186 -0
- package/dist/core/runtime/kickoff.d.ts +2 -1
- package/dist/core/runtime/kickoff.js +91 -5
- package/dist/core/runtime/launch.d.ts +54 -4
- package/dist/core/runtime/launch.js +85 -5
- package/dist/core/runtime/lifecycle.js +23 -6
- package/dist/core/runtime/memory.d.ts +2 -42
- package/dist/core/runtime/memory.js +11 -162
- package/dist/core/runtime/nodes.d.ts +37 -0
- package/dist/core/runtime/nodes.js +60 -1
- package/dist/core/runtime/persona.js +21 -11
- package/dist/core/runtime/pi-vendored.d.ts +18 -0
- package/dist/core/runtime/pi-vendored.js +49 -0
- package/dist/core/runtime/placement.d.ts +32 -14
- package/dist/core/runtime/placement.js +206 -52
- package/dist/core/runtime/promote.d.ts +0 -6
- package/dist/core/runtime/promote.js +4 -12
- package/dist/core/runtime/{demote.d.ts → recycle.d.ts} +5 -5
- package/dist/core/runtime/{demote.js → recycle.js} +27 -11
- package/dist/core/runtime/reset.js +11 -8
- package/dist/core/runtime/revive.d.ts +2 -0
- package/dist/core/runtime/revive.js +34 -34
- package/dist/core/runtime/spawn.d.ts +23 -0
- package/dist/core/runtime/spawn.js +77 -23
- package/dist/core/runtime/stop-guard.d.ts +1 -1
- package/dist/core/runtime/stop-guard.js +6 -1
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +1 -1
- package/dist/core/runtime/tmux.d.ts +28 -0
- package/dist/core/runtime/tmux.js +80 -6
- package/dist/core/scope.d.ts +11 -0
- package/dist/core/scope.js +39 -0
- package/dist/core/spawn.d.ts +5 -0
- package/dist/core/spawn.js +20 -2
- package/dist/core/substrate/gate.d.ts +13 -0
- package/dist/core/substrate/gate.js +21 -0
- package/dist/core/substrate/index.d.ts +7 -0
- package/dist/core/substrate/index.js +18 -0
- package/dist/core/substrate/on-read.d.ts +14 -0
- package/dist/core/substrate/on-read.js +292 -0
- package/dist/core/substrate/render.d.ts +25 -0
- package/dist/core/substrate/render.js +256 -0
- package/dist/core/substrate/schema.d.ts +76 -0
- package/dist/core/substrate/schema.js +124 -0
- package/dist/core/substrate/session-cache.d.ts +30 -0
- package/dist/core/substrate/session-cache.js +77 -0
- package/dist/core/substrate/subject.d.ts +41 -0
- package/dist/core/substrate/subject.js +54 -0
- package/dist/core/tui/contract.d.ts +83 -0
- package/dist/core/tui/contract.js +8 -0
- package/dist/core/tui/draw.d.ts +96 -0
- package/dist/core/tui/draw.js +339 -0
- package/dist/core/tui/host.d.ts +29 -0
- package/dist/core/tui/host.js +379 -0
- package/dist/core/tui/loader.d.ts +16 -0
- package/dist/core/tui/loader.js +94 -0
- package/dist/core/{canvas/browse → tui}/terminal.js +7 -6
- package/dist/core/wake.d.ts +86 -0
- package/dist/core/wake.js +308 -0
- package/dist/daemon/crtrd.d.ts +29 -4
- package/dist/daemon/crtrd.js +662 -46
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +171 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +19 -12
- package/dist/pi-extensions/canvas-commands.d.ts +3 -0
- package/dist/pi-extensions/canvas-commands.js +10 -0
- package/dist/pi-extensions/canvas-context-intro.d.ts +17 -0
- package/dist/pi-extensions/canvas-context-intro.js +55 -15
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +44 -0
- package/dist/pi-extensions/canvas-doc-substrate.js +112 -0
- package/dist/pi-extensions/canvas-nav.d.ts +3 -0
- package/dist/pi-extensions/canvas-nav.js +87 -23
- package/dist/pi-extensions/canvas-stophook.js +17 -8
- package/dist/pi-extensions/canvas-view.d.ts +21 -0
- package/dist/pi-extensions/canvas-view.js +75 -0
- package/dist/prompts/skill.js +19 -26
- package/dist/prompts/view.d.ts +7 -0
- package/dist/prompts/view.js +101 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js +1 -0
- package/package.json +9 -4
- package/dist/commands/skill/find.d.ts +0 -4
- package/dist/commands/skill/find.js +0 -257
- package/dist/commands/skill/read.js +0 -91
- /package/dist/commands/{skill → memory}/read.d.ts +0 -0
- /package/dist/core/{canvas/browse → tui}/terminal.d.ts +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Key } from './terminal.js';
|
|
2
|
+
import type { Draw, Rect } from './draw.js';
|
|
3
|
+
export interface ViewManifest {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
description: string;
|
|
8
|
+
refreshMs?: number;
|
|
9
|
+
keymap?: KeyHint[];
|
|
10
|
+
}
|
|
11
|
+
export interface KeyHint {
|
|
12
|
+
keys: string;
|
|
13
|
+
label: string;
|
|
14
|
+
}
|
|
15
|
+
/** Severity of a {@link ViewHost.setBanner} banner — drives glyph + hue + the
|
|
16
|
+
* derived state chip (error→blocked/red, action→attention/yellow, info→neutral). */
|
|
17
|
+
export type BannerLevel = 'info' | 'action' | 'error';
|
|
18
|
+
/** The current host banner, threaded into {@link ViewModule.dump} so the static
|
|
19
|
+
* (non-TTY) path can surface guidance without the view mirroring it into state. */
|
|
20
|
+
export interface Banner {
|
|
21
|
+
msg: string;
|
|
22
|
+
level: BannerLevel;
|
|
23
|
+
}
|
|
24
|
+
/** Optional host context passed to {@link ViewModule.dump} on the piped path. */
|
|
25
|
+
export interface DumpContext {
|
|
26
|
+
banner: Banner | null;
|
|
27
|
+
}
|
|
28
|
+
export interface ViewHost {
|
|
29
|
+
/** CLI flags forwarded verbatim, e.g. { port: '9222', target: '...' }. */
|
|
30
|
+
readonly options: Readonly<Record<string, string>>;
|
|
31
|
+
/** Transient status line (left of the footer): "Loading…", "Sent". */
|
|
32
|
+
setStatus(msg: string | null): void;
|
|
33
|
+
/** Severity-coded guidance banner above the footer (info/action/error). The
|
|
34
|
+
* level drives the banner glyph + hue AND the derived title state chip. */
|
|
35
|
+
setBanner(msg: string, level: BannerLevel): void;
|
|
36
|
+
/** Sticky error banner above the footer; null clears. Back-compat shorthand
|
|
37
|
+
* for setBanner(msg, 'error'). */
|
|
38
|
+
setError(msg: string | null): void;
|
|
39
|
+
/** Dynamic title subtitle: a dim ` · <s>` after the bold title (e.g. the live
|
|
40
|
+
* unread count "3 unread"). A non-null value OVERRIDES the static
|
|
41
|
+
* {@link ViewManifest.subtitle}; `null` clears back to the manifest default
|
|
42
|
+
* (or nothing). The title still leads — the subtitle is muted. */
|
|
43
|
+
setSubtitle(s: string | null): void;
|
|
44
|
+
/** Explicit interaction-mode chip override (compose/react). When set it WINS
|
|
45
|
+
* the title state chip over the derived state, rendering the mode word in the
|
|
46
|
+
* compose accent (yellow `33` bold) with the `✎` glyph (`react` → `☺`), so
|
|
47
|
+
* entering an input mode is unmistakable. `null` returns to derived behavior. */
|
|
48
|
+
setMode(mode: string | null): void;
|
|
49
|
+
}
|
|
50
|
+
export type ViewAction = {
|
|
51
|
+
type: 'render';
|
|
52
|
+
} | {
|
|
53
|
+
type: 'refresh';
|
|
54
|
+
} | {
|
|
55
|
+
type: 'quit';
|
|
56
|
+
} | {
|
|
57
|
+
type: 'none';
|
|
58
|
+
};
|
|
59
|
+
export interface ViewKey {
|
|
60
|
+
input: string;
|
|
61
|
+
key: Key;
|
|
62
|
+
}
|
|
63
|
+
export interface ViewModule<S = unknown> {
|
|
64
|
+
manifest: ViewManifest;
|
|
65
|
+
/** Build initial state. CHEAP + synchronous-ish — no screen, no slow fetch.
|
|
66
|
+
* Slow data loads on the first refresh() so the host can paint a loading state. */
|
|
67
|
+
init(host: ViewHost): S | Promise<S>;
|
|
68
|
+
/** Fetch/poll. Mutates state in place. Host calls it on launch, on `refreshMs`,
|
|
69
|
+
* and whenever a hook returns { type:'refresh' }. The host runs it in the
|
|
70
|
+
* single-flight lane (busy indicator; never re-entrant). */
|
|
71
|
+
refresh?(state: S, host: ViewHost): Promise<void>;
|
|
72
|
+
/** Paint the view into `content` (the body rect, host chrome excluded).
|
|
73
|
+
* Pure: reads state, calls draw.*; returns nothing. NEVER writes ANSI. */
|
|
74
|
+
render(state: S, draw: Draw, content: Rect): void;
|
|
75
|
+
/** Handle one keystroke. Mutates state; returns the next action. May be async
|
|
76
|
+
* (open thread / send) — the host serializes async hooks in the single lane. */
|
|
77
|
+
onKey?(k: ViewKey, state: S, host: ViewHost): ViewAction | Promise<ViewAction>;
|
|
78
|
+
/** Static text for the non-TTY / piped path (exit 0). Snapshot of current
|
|
79
|
+
* state. The host threads its current banner via the optional `ctx` so a view
|
|
80
|
+
* can surface guidance without mirroring it into state (older views ignore the
|
|
81
|
+
* arg and read their own state). */
|
|
82
|
+
dump(state: S, ctx?: DumpContext): string;
|
|
83
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// contract.ts — the view contract every `crtr view` module implements.
|
|
2
|
+
//
|
|
3
|
+
// A view is a self-contained ESM module whose DEFAULT export is a `ViewModule`.
|
|
4
|
+
// State is a single mutable object the view owns (mirrors browse/app.ts's
|
|
5
|
+
// BrowseState); hooks mutate it in place and return a `ViewAction` telling the
|
|
6
|
+
// host what to do next. The host injects `Draw` (from ./draw) + `ViewHost`; a
|
|
7
|
+
// view imports NOTHING from crtr internals.
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export interface Size {
|
|
2
|
+
cols: number;
|
|
3
|
+
rows: number;
|
|
4
|
+
}
|
|
5
|
+
export interface Rect {
|
|
6
|
+
row: number;
|
|
7
|
+
col: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
export interface Style {
|
|
12
|
+
fg?: string;
|
|
13
|
+
bg?: string;
|
|
14
|
+
bold?: boolean;
|
|
15
|
+
dim?: boolean;
|
|
16
|
+
reverse?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface Span {
|
|
19
|
+
text: string;
|
|
20
|
+
style?: Style;
|
|
21
|
+
}
|
|
22
|
+
/** An SGR fg/bg parameter is digits and semicolons only (e.g. '32', '1;36', '236').
|
|
23
|
+
* Guards styleSpan against a non-numeric value (a color name) producing a broken
|
|
24
|
+
* escape sequence. */
|
|
25
|
+
export declare function isSgrParams(v: string): boolean;
|
|
26
|
+
/** Style one chunk of text. Hue (fg/bg) is gated on `color`; bold/dim/reverse
|
|
27
|
+
* are not. After the styled text we return to `lineBase` (not a bare reset) so a
|
|
28
|
+
* row-level background/dim persists across spans instead of bleeding or being
|
|
29
|
+
* cleared. */
|
|
30
|
+
export declare function styleSpan(text: string, style: Style | undefined, color: boolean, lineBase: string): string;
|
|
31
|
+
/** Assemble styled spans into one line clipped to `width` visible cols. When
|
|
32
|
+
* `fill`, pad the remainder with spaces (under `lineBase`) so a cursor-row
|
|
33
|
+
* background spans the full width. Always RESET-terminated so no color bleeds
|
|
34
|
+
* into the next line. */
|
|
35
|
+
export declare function assemble(spans: Span[], width: number, color: boolean, lineBase: string, fill: boolean): string;
|
|
36
|
+
/** Truncate to `max` visible cols (plain text, no ANSI). */
|
|
37
|
+
export declare function clip(text: string, max: number): string;
|
|
38
|
+
export interface ColorCaps {
|
|
39
|
+
/** Any hue (fg/bg color) allowed. */
|
|
40
|
+
color: boolean;
|
|
41
|
+
/** 256-color bg allowed — drives the subtle cursor-row background. */
|
|
42
|
+
color256: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Detect color capability. Honors `NO_COLOR` and `TERM=dumb`, and only emits
|
|
45
|
+
* hue when stdout is a TTY. `color256` additionally requires a 256/truecolor
|
|
46
|
+
* terminal (for the cursor-row background; otherwise we fall back to reverse). */
|
|
47
|
+
export declare function detectColorCaps(stream?: {
|
|
48
|
+
isTTY?: boolean;
|
|
49
|
+
}, env?: NodeJS.ProcessEnv): ColorCaps;
|
|
50
|
+
/** One pre-styled list row (the view styles its own spans; `list` windows + the
|
|
51
|
+
* cursor highlight are the host's job). An optional `right` group is flush-right
|
|
52
|
+
* on the row (e.g. a timestamp) via spansRight; the left `spans` are clipped to
|
|
53
|
+
* leave room for it, and the cursor-row highlight merges over both. */
|
|
54
|
+
export interface ListItemRow {
|
|
55
|
+
spans: Span[];
|
|
56
|
+
right?: Span[];
|
|
57
|
+
}
|
|
58
|
+
/** Adjusted scroll the view stores back so the cursor stays visible. */
|
|
59
|
+
export interface ListResult {
|
|
60
|
+
scroll: number;
|
|
61
|
+
}
|
|
62
|
+
export interface Draw {
|
|
63
|
+
readonly size: Size;
|
|
64
|
+
readonly caps: ColorCaps;
|
|
65
|
+
/** Styled spans at an absolute cell, clipped to maxWidth (default → edge). */
|
|
66
|
+
spans(row: number, col: number, spans: Span[], maxWidth?: number): void;
|
|
67
|
+
/** Flush-right span placement (mirror of `spans`): the group's last visible
|
|
68
|
+
* cell lands just before `rightCol` (i.e. start col = rightCol − visibleWidth,
|
|
69
|
+
* rightCol exclusive). If the group exceeds `maxWidth` (default → the room left
|
|
70
|
+
* of rightCol) the LEFT end is clipped with a leading `…`. */
|
|
71
|
+
spansRight(row: number, rightCol: number, spans: Span[], maxWidth?: number): void;
|
|
72
|
+
/** Convenience single span. */
|
|
73
|
+
text(row: number, col: number, text: string, style?: Style): void;
|
|
74
|
+
/** Dim horizontal rule across [fromCol,toCol) (default full width). */
|
|
75
|
+
hline(row: number, fromCol?: number, toCol?: number, ch?: string): void;
|
|
76
|
+
/** Dim vertical rule down column `col` across [fromRow,toRow) (default full
|
|
77
|
+
* height). `ch` defaults to `│`; pass `|` as the ASCII fallback. */
|
|
78
|
+
vline(col: number, fromRow?: number, toRow?: number, ch?: string): void;
|
|
79
|
+
/** Single-line box border around rect (optional title in the top edge). */
|
|
80
|
+
box(rect: Rect, title?: string): void;
|
|
81
|
+
/** Split a rect into N columns by weights. */
|
|
82
|
+
columns(rect: Rect, weights: number[]): Rect[];
|
|
83
|
+
/** Scrollable list within rect: windows `items` to fit height, highlights the
|
|
84
|
+
* cursor row (256-bg or reverse fallback, like browse). Returns adjusted
|
|
85
|
+
* scroll so the cursor stays visible — the view stores it in state. */
|
|
86
|
+
list(rect: Rect, items: ListItemRow[], cursor: number, scroll: number): ListResult;
|
|
87
|
+
}
|
|
88
|
+
/** A live Draw plus the host-side serializer. */
|
|
89
|
+
export interface DrawHandle {
|
|
90
|
+
draw: Draw;
|
|
91
|
+
/** Serialize the buffer to a full repaint frame (home + per-line clear + clear
|
|
92
|
+
* below) — identical framing to browse's renderFrame. */
|
|
93
|
+
frame(): string;
|
|
94
|
+
}
|
|
95
|
+
/** Create a screen-sized cell buffer + the absolute-cell Draw API over it. */
|
|
96
|
+
export declare function createDraw(size: Size, caps: ColorCaps): DrawHandle;
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// draw.ts — the generic immediate-mode drawing surface shared by every crtr
|
|
2
|
+
// raw-ANSI TUI (the `crtr view` host and `canvas browse`).
|
|
3
|
+
//
|
|
4
|
+
// The span/color primitives (Span, styleSpan, assemble, clip, ColorCaps,
|
|
5
|
+
// detectColorCaps) are extracted verbatim-in-behavior from
|
|
6
|
+
// canvas/browse/render.ts so there is ONE copy; browse imports them back.
|
|
7
|
+
//
|
|
8
|
+
// On top of those primitives this module adds the `Draw` factory: a line/cell
|
|
9
|
+
// buffer covering the screen that a view fills via absolute-cell helpers
|
|
10
|
+
// (spans/text/hline/box/columns/list). The host allocates one Draw per frame,
|
|
11
|
+
// draws its chrome into it, hands the view a content Rect, then serializes the
|
|
12
|
+
// buffer to a single repaint frame (`\x1b[H` + per-line `\x1b[K` + `\x1b[J`) —
|
|
13
|
+
// exactly like browse's renderFrame does today.
|
|
14
|
+
//
|
|
15
|
+
// COLOR: hue (fg/bg) is gated on `caps.color`; structural SGR (bold/dim/reverse)
|
|
16
|
+
// is always allowed. 256-color bg (the cursor-row highlight) additionally needs
|
|
17
|
+
// `caps.color256`, falling back to reverse.
|
|
18
|
+
// ── ANSI ────────────────────────────────────────────────────────────────────
|
|
19
|
+
const ESC = '\x1b[';
|
|
20
|
+
const RESET = `${ESC}0m`;
|
|
21
|
+
const REVERSE = `${ESC}7m`;
|
|
22
|
+
const DIM = `${ESC}2m`;
|
|
23
|
+
const BOLD = `${ESC}1m`;
|
|
24
|
+
/** An SGR fg/bg parameter is digits and semicolons only (e.g. '32', '1;36', '236').
|
|
25
|
+
* Guards styleSpan against a non-numeric value (a color name) producing a broken
|
|
26
|
+
* escape sequence. */
|
|
27
|
+
export function isSgrParams(v) {
|
|
28
|
+
return v.length > 0 && /^[0-9;]+$/.test(v);
|
|
29
|
+
}
|
|
30
|
+
/** Style one chunk of text. Hue (fg/bg) is gated on `color`; bold/dim/reverse
|
|
31
|
+
* are not. After the styled text we return to `lineBase` (not a bare reset) so a
|
|
32
|
+
* row-level background/dim persists across spans instead of bleeding or being
|
|
33
|
+
* cleared. */
|
|
34
|
+
export function styleSpan(text, style, color, lineBase) {
|
|
35
|
+
if (text === '')
|
|
36
|
+
return '';
|
|
37
|
+
let pre = '';
|
|
38
|
+
if (style?.dim)
|
|
39
|
+
pre += DIM;
|
|
40
|
+
if (style?.bold)
|
|
41
|
+
pre += BOLD;
|
|
42
|
+
if (style?.reverse)
|
|
43
|
+
pre += REVERSE;
|
|
44
|
+
// fg is an SGR parameter string (e.g. '32' or '1;36'); bg is a 256-color index.
|
|
45
|
+
// Reject anything else so a bad value (e.g. a color NAME like 'green') degrades
|
|
46
|
+
// to no-color instead of emitting an invalid CSI that prints as literal garbage.
|
|
47
|
+
if (color && style?.fg && isSgrParams(style.fg))
|
|
48
|
+
pre += `${ESC}${style.fg}m`;
|
|
49
|
+
if (color && style?.bg && isSgrParams(style.bg))
|
|
50
|
+
pre += `${ESC}48;5;${style.bg}m`;
|
|
51
|
+
if (pre === '')
|
|
52
|
+
return text; // inherits lineBase / default
|
|
53
|
+
return `${pre}${text}${RESET}${lineBase}`;
|
|
54
|
+
}
|
|
55
|
+
/** Assemble styled spans into one line clipped to `width` visible cols. When
|
|
56
|
+
* `fill`, pad the remainder with spaces (under `lineBase`) so a cursor-row
|
|
57
|
+
* background spans the full width. Always RESET-terminated so no color bleeds
|
|
58
|
+
* into the next line. */
|
|
59
|
+
export function assemble(spans, width, color, lineBase, fill) {
|
|
60
|
+
let used = 0;
|
|
61
|
+
let body = '';
|
|
62
|
+
for (const span of spans) {
|
|
63
|
+
if (used >= width)
|
|
64
|
+
break;
|
|
65
|
+
if (span.text === '')
|
|
66
|
+
continue;
|
|
67
|
+
let t = span.text;
|
|
68
|
+
const remaining = width - used;
|
|
69
|
+
let cut = false;
|
|
70
|
+
if (t.length > remaining) {
|
|
71
|
+
t = t.slice(0, Math.max(0, remaining - 1)) + '…';
|
|
72
|
+
cut = true;
|
|
73
|
+
}
|
|
74
|
+
body += styleSpan(t, span.style, color, lineBase);
|
|
75
|
+
used += t.length;
|
|
76
|
+
if (cut)
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
if (fill && used < width)
|
|
80
|
+
body += ' '.repeat(width - used);
|
|
81
|
+
return lineBase === '' ? body : `${lineBase}${body}${RESET}`;
|
|
82
|
+
}
|
|
83
|
+
/** Truncate to `max` visible cols (plain text, no ANSI). */
|
|
84
|
+
export function clip(text, max) {
|
|
85
|
+
if (max <= 0)
|
|
86
|
+
return '';
|
|
87
|
+
return text.length <= max ? text : text.slice(0, Math.max(0, max - 1)) + '…';
|
|
88
|
+
}
|
|
89
|
+
/** Detect color capability. Honors `NO_COLOR` and `TERM=dumb`, and only emits
|
|
90
|
+
* hue when stdout is a TTY. `color256` additionally requires a 256/truecolor
|
|
91
|
+
* terminal (for the cursor-row background; otherwise we fall back to reverse). */
|
|
92
|
+
export function detectColorCaps(stream = process.stdout, env = process.env) {
|
|
93
|
+
const term = env['TERM'] ?? '';
|
|
94
|
+
const color = stream.isTTY === true && !env['NO_COLOR'] && term !== 'dumb';
|
|
95
|
+
const colorTerm = env['COLORTERM'] ?? '';
|
|
96
|
+
const color256 = color && (/256|direct/i.test(term) || /truecolor|24bit/i.test(colorTerm));
|
|
97
|
+
return { color, color256 };
|
|
98
|
+
}
|
|
99
|
+
function mergeStyle(base, top) {
|
|
100
|
+
if (!top)
|
|
101
|
+
return base;
|
|
102
|
+
return {
|
|
103
|
+
fg: top.fg ?? base.fg,
|
|
104
|
+
bg: top.bg ?? base.bg,
|
|
105
|
+
bold: top.bold || base.bold,
|
|
106
|
+
dim: top.dim || base.dim,
|
|
107
|
+
reverse: top.reverse || base.reverse,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function sameStyle(a, b) {
|
|
111
|
+
if (a === b)
|
|
112
|
+
return true;
|
|
113
|
+
if (!a || !b)
|
|
114
|
+
return false;
|
|
115
|
+
return a.fg === b.fg && a.bg === b.bg
|
|
116
|
+
&& !!a.bold === !!b.bold && !!a.dim === !!b.dim && !!a.reverse === !!b.reverse;
|
|
117
|
+
}
|
|
118
|
+
/** Create a screen-sized cell buffer + the absolute-cell Draw API over it. */
|
|
119
|
+
export function createDraw(size, caps) {
|
|
120
|
+
const cols = Math.max(1, size.cols);
|
|
121
|
+
const rows = Math.max(1, size.rows);
|
|
122
|
+
const grid = [];
|
|
123
|
+
for (let r = 0; r < rows; r++) {
|
|
124
|
+
const line = new Array(cols);
|
|
125
|
+
for (let c = 0; c < cols; c++)
|
|
126
|
+
line[c] = { ch: ' ', style: undefined };
|
|
127
|
+
grid.push(line);
|
|
128
|
+
}
|
|
129
|
+
/** Place spans starting at (row, col), clipped to `limit` visible cells; each
|
|
130
|
+
* span's style optionally merged under `base` (the cursor-row highlight). */
|
|
131
|
+
const place = (row, col, spans, maxWidth, base) => {
|
|
132
|
+
if (row < 0 || row >= rows)
|
|
133
|
+
return;
|
|
134
|
+
const edge = cols - col;
|
|
135
|
+
const limit = Math.min(maxWidth ?? edge, edge);
|
|
136
|
+
if (limit <= 0)
|
|
137
|
+
return;
|
|
138
|
+
let x = col;
|
|
139
|
+
let drawn = 0;
|
|
140
|
+
for (const span of spans) {
|
|
141
|
+
if (drawn >= limit)
|
|
142
|
+
break;
|
|
143
|
+
const style = base ? mergeStyle(base, span.style) : span.style;
|
|
144
|
+
for (const ch of Array.from(span.text)) {
|
|
145
|
+
if (drawn >= limit)
|
|
146
|
+
break;
|
|
147
|
+
if (x >= 0 && x < cols)
|
|
148
|
+
grid[row][x] = { ch, style };
|
|
149
|
+
x++;
|
|
150
|
+
drawn++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const fillStyle = (row, col, width, style) => {
|
|
155
|
+
if (row < 0 || row >= rows)
|
|
156
|
+
return;
|
|
157
|
+
const end = Math.min(cols, col + width);
|
|
158
|
+
for (let x = Math.max(0, col); x < end; x++)
|
|
159
|
+
grid[row][x] = { ch: ' ', style };
|
|
160
|
+
};
|
|
161
|
+
/** Visible (column) width of a span group — ANSI-free cell count. */
|
|
162
|
+
const spanWidth = (spans) => {
|
|
163
|
+
let n = 0;
|
|
164
|
+
for (const s of spans)
|
|
165
|
+
n += Array.from(s.text).length;
|
|
166
|
+
return n;
|
|
167
|
+
};
|
|
168
|
+
/** Flatten spans to per-cell {ch,style}, then re-group consecutive same-style
|
|
169
|
+
* cells back into spans — used to left-clip a flush-right group. */
|
|
170
|
+
const regroup = (cells) => {
|
|
171
|
+
const out = [];
|
|
172
|
+
for (const c of cells) {
|
|
173
|
+
const last = out[out.length - 1];
|
|
174
|
+
if (last && sameStyle(last.style, c.style))
|
|
175
|
+
last.text += c.ch;
|
|
176
|
+
else
|
|
177
|
+
out.push({ text: c.ch, style: c.style });
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
};
|
|
181
|
+
/** Flush-right placement shared by draw.spansRight and the list right-group.
|
|
182
|
+
* Lays `spans` so the group ends just before `rightCol` (exclusive); clips the
|
|
183
|
+
* LEFT end with a leading `…` when the group exceeds the effective width.
|
|
184
|
+
* `base` (the cursor-row highlight) merges under each cell, like `place`. */
|
|
185
|
+
const placeRight = (row, rightCol, spans, maxWidth, base) => {
|
|
186
|
+
if (row < 0 || row >= rows)
|
|
187
|
+
return;
|
|
188
|
+
const cap = Math.max(0, rightCol); // room available left of rightCol
|
|
189
|
+
const max = Math.min(maxWidth ?? cap, cap);
|
|
190
|
+
if (max <= 0)
|
|
191
|
+
return;
|
|
192
|
+
let cells = [];
|
|
193
|
+
for (const s of spans)
|
|
194
|
+
for (const ch of Array.from(s.text))
|
|
195
|
+
cells.push({ ch, style: s.style });
|
|
196
|
+
if (cells.length === 0)
|
|
197
|
+
return;
|
|
198
|
+
if (cells.length > max) {
|
|
199
|
+
const drop = cells.length - max + 1; // make room for the leading …
|
|
200
|
+
cells = cells.slice(drop);
|
|
201
|
+
cells.unshift({ ch: '…', style: cells[0]?.style });
|
|
202
|
+
}
|
|
203
|
+
const startCol = rightCol - cells.length;
|
|
204
|
+
place(row, startCol, regroup(cells), undefined, base);
|
|
205
|
+
};
|
|
206
|
+
const draw = {
|
|
207
|
+
size: { cols, rows },
|
|
208
|
+
caps,
|
|
209
|
+
spans(row, col, spans, maxWidth) {
|
|
210
|
+
place(row, col, spans, maxWidth);
|
|
211
|
+
},
|
|
212
|
+
spansRight(row, rightCol, spans, maxWidth) {
|
|
213
|
+
placeRight(row, rightCol, spans, maxWidth);
|
|
214
|
+
},
|
|
215
|
+
text(row, col, text, style) {
|
|
216
|
+
place(row, col, [{ text, style }], undefined);
|
|
217
|
+
},
|
|
218
|
+
hline(row, fromCol = 0, toCol = cols, ch = '─') {
|
|
219
|
+
if (row < 0 || row >= rows)
|
|
220
|
+
return;
|
|
221
|
+
const a = Math.max(0, Math.min(cols, fromCol));
|
|
222
|
+
const b = Math.max(0, Math.min(cols, toCol));
|
|
223
|
+
for (let x = a; x < b; x++)
|
|
224
|
+
grid[row][x] = { ch, style: { dim: true } };
|
|
225
|
+
},
|
|
226
|
+
vline(col, fromRow = 0, toRow = rows, ch = '│') {
|
|
227
|
+
if (col < 0 || col >= cols)
|
|
228
|
+
return;
|
|
229
|
+
const a = Math.max(0, Math.min(rows, fromRow));
|
|
230
|
+
const b = Math.max(0, Math.min(rows, toRow));
|
|
231
|
+
for (let r = a; r < b; r++)
|
|
232
|
+
grid[r][col] = { ch, style: { dim: true } };
|
|
233
|
+
},
|
|
234
|
+
box(rect, title) {
|
|
235
|
+
const { row, col, width, height } = rect;
|
|
236
|
+
if (width < 2 || height < 2)
|
|
237
|
+
return;
|
|
238
|
+
const bs = { dim: true };
|
|
239
|
+
const right = col + width - 1;
|
|
240
|
+
const bottom = row + height - 1;
|
|
241
|
+
const horiz = '─'.repeat(Math.max(0, width - 2));
|
|
242
|
+
place(row, col, [{ text: `┌${horiz}┐`, style: bs }], width);
|
|
243
|
+
place(bottom, col, [{ text: `└${horiz}┘`, style: bs }], width);
|
|
244
|
+
for (let r = row + 1; r < bottom; r++) {
|
|
245
|
+
place(r, col, [{ text: '│', style: bs }], 1);
|
|
246
|
+
place(r, right, [{ text: '│', style: bs }], 1);
|
|
247
|
+
}
|
|
248
|
+
if (title !== undefined && title !== '' && width > 4) {
|
|
249
|
+
place(row, col + 2, [{ text: ` ${title} ` }], width - 4);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
columns(rect, weights) {
|
|
253
|
+
const n = weights.length;
|
|
254
|
+
if (n === 0)
|
|
255
|
+
return [];
|
|
256
|
+
const total = weights.reduce((a, b) => a + (b > 0 ? b : 0), 0) || 1;
|
|
257
|
+
const out = [];
|
|
258
|
+
let x = rect.col;
|
|
259
|
+
let used = 0;
|
|
260
|
+
for (let i = 0; i < n; i++) {
|
|
261
|
+
const isLast = i === n - 1;
|
|
262
|
+
const w = isLast
|
|
263
|
+
? Math.max(0, rect.width - used)
|
|
264
|
+
: Math.max(0, Math.floor((rect.width * Math.max(0, weights[i])) / total));
|
|
265
|
+
out.push({ row: rect.row, col: x, width: w, height: rect.height });
|
|
266
|
+
x += w;
|
|
267
|
+
used += w;
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
},
|
|
271
|
+
list(rect, items, cursor, scroll) {
|
|
272
|
+
const height = Math.max(0, rect.height);
|
|
273
|
+
let sc = scroll;
|
|
274
|
+
if (height > 0) {
|
|
275
|
+
if (cursor < sc)
|
|
276
|
+
sc = cursor;
|
|
277
|
+
if (cursor >= sc + height)
|
|
278
|
+
sc = cursor - height + 1;
|
|
279
|
+
}
|
|
280
|
+
if (sc < 0)
|
|
281
|
+
sc = 0;
|
|
282
|
+
if (sc > Math.max(0, items.length - 1))
|
|
283
|
+
sc = Math.max(0, items.length - 1);
|
|
284
|
+
const end = Math.min(items.length, sc + height);
|
|
285
|
+
for (let i = sc; i < end; i++) {
|
|
286
|
+
const rowIdx = rect.row + (i - sc);
|
|
287
|
+
const item = items[i];
|
|
288
|
+
const base = i === cursor
|
|
289
|
+
? (caps.color256 ? { bg: '236' } : { reverse: true })
|
|
290
|
+
: undefined;
|
|
291
|
+
if (base)
|
|
292
|
+
fillStyle(rowIdx, rect.col, rect.width, base);
|
|
293
|
+
if (item.right && item.right.length) {
|
|
294
|
+
// Right-flush group (e.g. a timestamp); clip the left spans so they
|
|
295
|
+
// never collide, and merge the cursor highlight over both.
|
|
296
|
+
const rightWidth = spanWidth(item.right);
|
|
297
|
+
const leftLimit = Math.max(0, rect.width - rightWidth - 1);
|
|
298
|
+
place(rowIdx, rect.col, item.spans, leftLimit, base);
|
|
299
|
+
placeRight(rowIdx, rect.col + rect.width, item.right, rightWidth, base);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
place(rowIdx, rect.col, item.spans, rect.width, base);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return { scroll: sc };
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
const serializeRow = (cells) => {
|
|
309
|
+
// Trim trailing plain blanks (clear-to-EOL covers them); keep styled tails
|
|
310
|
+
// (e.g. a cursor-row background fill) intact.
|
|
311
|
+
let last = -1;
|
|
312
|
+
for (let i = cells.length - 1; i >= 0; i--) {
|
|
313
|
+
const c = cells[i];
|
|
314
|
+
if (c.ch !== ' ' || c.style !== undefined) {
|
|
315
|
+
last = i;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (last < 0)
|
|
320
|
+
return '';
|
|
321
|
+
let out = '';
|
|
322
|
+
let i = 0;
|
|
323
|
+
while (i <= last) {
|
|
324
|
+
const style = cells[i].style;
|
|
325
|
+
let text = '';
|
|
326
|
+
while (i <= last && sameStyle(cells[i].style, style)) {
|
|
327
|
+
text += cells[i].ch;
|
|
328
|
+
i++;
|
|
329
|
+
}
|
|
330
|
+
out += styleSpan(text, style, caps.color, '');
|
|
331
|
+
}
|
|
332
|
+
return out;
|
|
333
|
+
};
|
|
334
|
+
const frame = () => {
|
|
335
|
+
const body = grid.map((cells) => `${serializeRow(cells)}${ESC}K`).join('\r\n');
|
|
336
|
+
return `${ESC}H${body}${ESC}J`;
|
|
337
|
+
};
|
|
338
|
+
return { draw, frame };
|
|
339
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Draw, type Rect, type Size } from './draw.js';
|
|
2
|
+
import type { ViewModule, ViewManifest, BannerLevel } from './contract.js';
|
|
3
|
+
export interface RunViewOptions {
|
|
4
|
+
/** CLI flags forwarded verbatim to the view via host.options. */
|
|
5
|
+
options?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
/** The host-tracked chrome state. `banner` + `busy` + `loaded` derive the title
|
|
8
|
+
* state chip; `tick` advances the spinner under the busy-tick repaint. */
|
|
9
|
+
export interface Chrome {
|
|
10
|
+
status: string | null;
|
|
11
|
+
banner: {
|
|
12
|
+
msg: string;
|
|
13
|
+
level: BannerLevel;
|
|
14
|
+
} | null;
|
|
15
|
+
busy: boolean;
|
|
16
|
+
loaded: boolean;
|
|
17
|
+
lastRefresh: number;
|
|
18
|
+
tick: number;
|
|
19
|
+
subtitle: string | null;
|
|
20
|
+
mode: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** Draw the host chrome into `draw` and return the content Rect for the view.
|
|
23
|
+
* Three zones: title row (state rail + title/subtitle + state chip + liveness),
|
|
24
|
+
* a hairline, and a footer (status left, keymap right) with a severity banner
|
|
25
|
+
* on the row above it when set. */
|
|
26
|
+
declare function drawChrome(draw: Draw, size: Size, manifest: ViewManifest, c: Chrome, now?: number): Rect;
|
|
27
|
+
export { drawChrome };
|
|
28
|
+
/** Host a view in the alt screen until it quits (or Ctrl-C). */
|
|
29
|
+
export declare function runView<S>(view: ViewModule<S>, opts?: RunViewOptions): Promise<void>;
|