open-multi-agent-kit 0.91.0 → 0.93.0
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/CHANGELOG.md +32 -1
- package/README.md +2 -2
- package/dist/core/agent-session.d.ts +29 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +89 -21
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/command-safety.d.ts.map +1 -1
- package/dist/core/command-safety.js +0 -50
- package/dist/core/command-safety.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +5 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +5 -7
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +12 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +28 -15
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/transaction.d.ts.map +1 -1
- package/dist/core/compaction/transaction.js +7 -3
- package/dist/core/compaction/transaction.js.map +1 -1
- package/dist/core/context-budget-headroom-candidates.d.ts.map +1 -1
- package/dist/core/context-budget-headroom-candidates.js +21 -11
- package/dist/core/context-budget-headroom-candidates.js.map +1 -1
- package/dist/core/context-budget-system-prompt-items.d.ts +1 -0
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js +7 -8
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/extensions/builtin/command-safety-gate.d.ts +8 -0
- package/dist/core/extensions/builtin/command-safety-gate.d.ts.map +1 -1
- package/dist/core/extensions/builtin/command-safety-gate.js +23 -0
- package/dist/core/extensions/builtin/command-safety-gate.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +4 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/keybindings.d.ts +5 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +5 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/loadouts.d.ts.map +1 -1
- package/dist/core/loadouts.js +27 -48
- package/dist/core/loadouts.js.map +1 -1
- package/dist/core/model-resolver.d.ts +41 -3
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +17 -9
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +3 -1
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/reasoning-router-v4-weights.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4-weights.js +9 -3
- package/dist/core/reasoning-router-v4-weights.js.map +1 -1
- package/dist/core/reasoning-router-v4.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4.js +94 -24
- package/dist/core/reasoning-router-v4.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +11 -5
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/session-file-compaction.d.ts.map +1 -1
- package/dist/core/session-file-compaction.js +39 -5
- package/dist/core/session-file-compaction.js.map +1 -1
- package/dist/core/session-termination.d.ts.map +1 -1
- package/dist/core/session-termination.js +1 -1
- package/dist/core/session-termination.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +36 -31
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +38 -11
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +112 -11
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/control-panel-box.d.ts +27 -0
- package/dist/modes/interactive/components/control-panel-box.d.ts.map +1 -1
- package/dist/modes/interactive/components/control-panel-box.js +60 -8
- package/dist/modes/interactive/components/control-panel-box.js.map +1 -1
- package/dist/modes/interactive/components/control-panel-layout.d.ts.map +1 -1
- package/dist/modes/interactive/components/control-panel-layout.js +100 -45
- package/dist/modes/interactive/components/control-panel-layout.js.map +1 -1
- package/dist/modes/interactive/components/control-panel-runtime-status.d.ts +9 -0
- package/dist/modes/interactive/components/control-panel-runtime-status.d.ts.map +1 -1
- package/dist/modes/interactive/components/control-panel-runtime-status.js +10 -8
- package/dist/modes/interactive/components/control-panel-runtime-status.js.map +1 -1
- package/dist/modes/interactive/components/control-panel-sparkles.d.ts +5 -0
- package/dist/modes/interactive/components/control-panel-sparkles.d.ts.map +1 -1
- package/dist/modes/interactive/components/control-panel-sparkles.js +14 -9
- package/dist/modes/interactive/components/control-panel-sparkles.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +10 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +3 -3
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/status-sidebar.d.ts +49 -0
- package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -0
- package/dist/modes/interactive/components/status-sidebar.js +253 -0
- package/dist/modes/interactive/components/status-sidebar.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +9 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +94 -20
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/omk-neon-control.json +13 -13
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +9 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +19 -8
- package/dist/utils/clipboard.js.map +1 -1
- package/docs/adaptorch-preview-spec.md +174 -0
- package/docs/adaptorch-preview.md +1 -1
- package/docs/compaction.md +20 -13
- package/docs/correctness-wall.md +13 -5
- package/docs/custom-provider.md +8 -8
- package/docs/development.md +2 -2
- package/docs/docs.json +4 -0
- package/docs/environment-variables.md +90 -0
- package/docs/extensions.md +8 -8
- package/docs/index.md +1 -0
- package/docs/json.md +4 -4
- package/docs/providers.md +1 -1
- package/docs/rpc.md +5 -2
- package/docs/session-format.md +5 -5
- package/docs/settings.md +1 -0
- package/docs/skills.md +2 -0
- package/docs/themes.md +2 -2
- package/docs/tui.md +4 -4
- package/docs/usage.md +4 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +35 -10
- package/examples/extensions/subagent/adaptive-agent-runtime.test.ts +234 -0
- package/examples/extensions/subagent/adaptive-agent-runtime.ts +240 -0
- package/examples/extensions/subagent/adaptive-result.ts +112 -0
- package/examples/extensions/subagent/agents.test.ts +86 -0
- package/examples/extensions/subagent/agents.ts +18 -4
- package/examples/extensions/subagent/capabilities.ts +15 -1
- package/examples/extensions/subagent/checkpoint-runtime.test.ts +125 -0
- package/examples/extensions/subagent/checkpoint-runtime.ts +198 -0
- package/examples/extensions/subagent/deadline-budget.test.ts +135 -0
- package/examples/extensions/subagent/deadline-budget.ts +242 -0
- package/examples/extensions/subagent/deadline-profile-store.test.ts +72 -0
- package/examples/extensions/subagent/deadline-profile-store.ts +172 -0
- package/examples/extensions/subagent/domain-profiles.ts +272 -0
- package/examples/extensions/subagent/fixtures/fake-omk-json.mjs +13 -0
- package/examples/extensions/subagent/fixtures/hanging-process-tree.mjs +40 -0
- package/examples/extensions/subagent/index.ts +253 -137
- package/examples/extensions/subagent/managed-process.test.ts +178 -0
- package/examples/extensions/subagent/managed-process.ts +236 -0
- package/examples/extensions/subagent/subagent-extension-smoke.test.ts +112 -0
- package/examples/extensions/subagent/subagent-runtime-types.ts +76 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +21 -21
- package/package.json +10 -7
|
@@ -1,11 +1,38 @@
|
|
|
1
1
|
import { type ThemeColor } from "../theme/theme.ts";
|
|
2
2
|
export declare function composeColumns(leftLines: string[], leftWidth: number, rightLines: string[], rightWidth: number, gapWidth: number, width: number): string[];
|
|
3
|
+
/**
|
|
4
|
+
* Section rule for the control rail.
|
|
5
|
+
*
|
|
6
|
+
* Editorial treatment: the label sits flush left behind a single hairline tick
|
|
7
|
+
* and the rule runs out to the right edge, instead of a centered bold caption.
|
|
8
|
+
* Frames stay hairline so the accent colour is reserved for live values.
|
|
9
|
+
*/
|
|
3
10
|
export declare function sidebarRule(width: number, label: string, borderColor?: ThemeColor): string;
|
|
11
|
+
/** Width of the right-aligned label gutter shared by every control-rail row. */
|
|
12
|
+
export declare const RAIL_LABEL_COLUMN = 8;
|
|
13
|
+
/**
|
|
14
|
+
* Right-aligned `label:` gutter cell.
|
|
15
|
+
*
|
|
16
|
+
* Labels are right-aligned so every value starts on the same column while the
|
|
17
|
+
* rendered text keeps exactly one space after the colon (`route: active`),
|
|
18
|
+
* which downstream reference-fidelity assertions depend on.
|
|
19
|
+
*/
|
|
20
|
+
export declare function labelCell(label: string, column?: number): string;
|
|
4
21
|
export declare function boxTop(width: number, label: string, borderColor?: ThemeColor): string;
|
|
5
22
|
export declare function boxBottom(width: number, borderColor?: ThemeColor): string;
|
|
6
23
|
export declare function boxBlankLine(width: number): string;
|
|
24
|
+
/** Hairline rule inside a framed panel, inset from both frame edges. */
|
|
25
|
+
export declare function boxRuleLine(width: number, inset?: number): string;
|
|
7
26
|
export declare function boxCenteredLine(width: number, text: string, color?: ThemeColor): string;
|
|
8
27
|
export declare function boxTextLine(width: number, text: string, color?: ThemeColor): string;
|
|
28
|
+
/**
|
|
29
|
+
* Pad a framed column with blank body rows so two side-by-side panels close on
|
|
30
|
+
* the same row. The leading and trailing frame lines are preserved.
|
|
31
|
+
*
|
|
32
|
+
* `"center"` splits the filler above and below the body, which turns leftover
|
|
33
|
+
* height into deliberate negative space instead of a hollow bottom half.
|
|
34
|
+
*/
|
|
35
|
+
export declare function padBoxColumn(lines: string[], targetHeight: number, width: number, distribution?: "bottom" | "center"): string[];
|
|
9
36
|
export declare function centerLine(width: number, text: string, color?: ThemeColor): string;
|
|
10
37
|
export declare function centerText(width: number, text: string): string;
|
|
11
38
|
export declare function divider(width: number, label: string, color: ThemeColor): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-panel-box.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel-box.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAS,MAAM,mBAAmB,CAAC;AAE3D,wBAAgB,cAAc,CAC7B,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACX,MAAM,EAAE,CAOV;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,GAAE,
|
|
1
|
+
{"version":3,"file":"control-panel-box.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel-box.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAS,MAAM,mBAAmB,CAAC;AAE3D,wBAAgB,cAAc,CAC7B,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACX,MAAM,EAAE,CAOV;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,GAAE,UAA0B,GAAG,MAAM,CAQzG;AAED,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAA0B,GAAG,MAAM,CAInF;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,GAAE,UAA0B,GAAG,MAAM,CAOpG;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,GAAE,UAA0B,GAAG,MAAM,CAExF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,MAAM,CAI5D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,MAAM,CAEvF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,MAAM,CAMnF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC3B,KAAK,EAAE,MAAM,EAAE,EACf,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,YAAY,GAAE,QAAQ,GAAG,QAAmB,GAC1C,MAAM,EAAE,CAkBV;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,MAAM,CAElF;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAM/E;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,MAAM,CAMhF;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D","sourcesContent":["import { truncateToWidth, visibleWidth } from \"omk-tui\";\nimport { type ThemeColor, theme } from \"../theme/theme.ts\";\n\nexport function composeColumns(\n\tleftLines: string[],\n\tleftWidth: number,\n\trightLines: string[],\n\trightWidth: number,\n\tgapWidth: number,\n\twidth: number,\n): string[] {\n\treturn Array.from({ length: Math.max(leftLines.length, rightLines.length) }, (_, index) =>\n\t\tclipLine(\n\t\t\t`${fitLine(leftLines[index] ?? \"\", leftWidth)}${\" \".repeat(gapWidth)}${fitLine(rightLines[index] ?? \"\", rightWidth)}`,\n\t\t\twidth,\n\t\t),\n\t);\n}\n\n/**\n * Section rule for the control rail.\n *\n * Editorial treatment: the label sits flush left behind a single hairline tick\n * and the rule runs out to the right edge, instead of a centered bold caption.\n * Frames stay hairline so the accent colour is reserved for live values.\n */\nexport function sidebarRule(width: number, label: string, borderColor: ThemeColor = \"borderMuted\"): string {\n\tconst bodyWidth = Math.max(0, width - 2);\n\tconst labelText = ` ${label} `;\n\tconst fill = Math.max(0, bodyWidth - visibleWidth(labelText) - 1);\n\treturn clipLine(\n\t\t`${theme.fg(borderColor, \"\\u2502\")}${theme.fg(\"borderMuted\", \"\\u2500\")}${theme.fg(\"accent\", labelText)}${theme.fg(\"borderMuted\", \"\\u2500\".repeat(fill))}${theme.fg(borderColor, \"\\u2502\")}`,\n\t\twidth,\n\t);\n}\n\n/** Width of the right-aligned label gutter shared by every control-rail row. */\nexport const RAIL_LABEL_COLUMN = 8;\n\n/**\n * Right-aligned `label:` gutter cell.\n *\n * Labels are right-aligned so every value starts on the same column while the\n * rendered text keeps exactly one space after the colon (`route: active`),\n * which downstream reference-fidelity assertions depend on.\n */\nexport function labelCell(label: string, column: number = RAIL_LABEL_COLUMN): string {\n\tconst text = `${label}:`;\n\tconst pad = Math.max(0, column + 1 - visibleWidth(text));\n\treturn `${\" \".repeat(pad)}${theme.fg(\"muted\", text)}`;\n}\n\nexport function boxTop(width: number, label: string, borderColor: ThemeColor = \"borderMuted\"): string {\n\tconst text = ` ${label} `;\n\tconst fillWidth = Math.max(0, width - visibleWidth(\"+\") - visibleWidth(text) - visibleWidth(\"+\"));\n\treturn clipLine(\n\t\t`${theme.fg(borderColor, \"\\u256d\")}${theme.bold(theme.fg(\"accent\", text))}${theme.fg(borderColor, \"\\u2500\".repeat(fillWidth))}${theme.fg(borderColor, \"\\u256e\")}`,\n\t\twidth,\n\t);\n}\n\nexport function boxBottom(width: number, borderColor: ThemeColor = \"borderMuted\"): string {\n\treturn clipLine(theme.fg(borderColor, `\\u2570${\"\\u2500\".repeat(Math.max(0, width - 2))}\\u256f`), width);\n}\n\nexport function boxBlankLine(width: number): string {\n\treturn boxTextLine(width, \"\");\n}\n\n/** Hairline rule inside a framed panel, inset from both frame edges. */\nexport function boxRuleLine(width: number, inset = 0): string {\n\tconst innerWidth = Math.max(0, width - 4);\n\tconst ruleWidth = Math.max(0, innerWidth - inset * 2);\n\treturn boxTextLine(width, `${\" \".repeat(inset)}${theme.fg(\"borderMuted\", \"\\u2500\".repeat(ruleWidth))}`);\n}\n\nexport function boxCenteredLine(width: number, text: string, color?: ThemeColor): string {\n\treturn boxTextLine(width, centerText(Math.max(0, width - 4), color ? theme.fg(color, text) : text));\n}\n\nexport function boxTextLine(width: number, text: string, color?: ThemeColor): string {\n\tconst body = color ? theme.fg(color, text) : text;\n\treturn clipLine(\n\t\t`${theme.fg(\"borderMuted\", \"\\u2502 \")}${fitLine(body, Math.max(0, width - 4))}${theme.fg(\"borderMuted\", \" \\u2502\")}`,\n\t\twidth,\n\t);\n}\n\n/**\n * Pad a framed column with blank body rows so two side-by-side panels close on\n * the same row. The leading and trailing frame lines are preserved.\n *\n * `\"center\"` splits the filler above and below the body, which turns leftover\n * height into deliberate negative space instead of a hollow bottom half.\n */\nexport function padBoxColumn(\n\tlines: string[],\n\ttargetHeight: number,\n\twidth: number,\n\tdistribution: \"bottom\" | \"center\" = \"bottom\",\n): string[] {\n\tif (lines.length === 0 || lines.length >= targetHeight) return lines;\n\tconst top = lines[0] as string;\n\tconst bottom = lines[lines.length - 1] as string;\n\tconst body = lines.slice(1, -1);\n\tconst missing = targetHeight - lines.length;\n\tconst blank = () => boxTextLine(width, \"\");\n\tif (distribution === \"bottom\") {\n\t\treturn [top, ...body, ...Array.from({ length: missing }, blank), bottom];\n\t}\n\tconst above = Math.floor(missing / 2);\n\treturn [\n\t\ttop,\n\t\t...Array.from({ length: above }, blank),\n\t\t...body,\n\t\t...Array.from({ length: missing - above }, blank),\n\t\tbottom,\n\t];\n}\n\nexport function centerLine(width: number, text: string, color?: ThemeColor): string {\n\treturn clipLine(centerText(width, color ? theme.fg(color, text) : text), width);\n}\n\nexport function centerText(width: number, text: string): string {\n\tconst fitted = truncateToWidth(text, width, \"\");\n\tconst remaining = Math.max(0, width - visibleWidth(fitted));\n\treturn `${\" \".repeat(Math.floor(remaining / 2))}${fitted}${\" \".repeat(Math.ceil(remaining / 2))}`;\n}\n\nexport function divider(width: number, label: string, color: ThemeColor): string {\n\tconst fillWidth = Math.max(0, width - visibleWidth(\"+-- \") - visibleWidth(label) - visibleWidth(\" +\"));\n\treturn clipLine(\n\t\t`${theme.fg(\"border\", \"+-- \")}${theme.bold(theme.fg(color, label))}${theme.fg(\"border\", ` ${\"-\".repeat(fillWidth)}+`)}`,\n\t\twidth,\n\t);\n}\n\nexport function textLine(width: number, text: string, color?: ThemeColor): string {\n\tconst body = color ? theme.fg(color, text) : text;\n\treturn clipLine(\n\t\t`${theme.fg(\"borderMuted\", \"\\u2502 \")}${fitLine(body, Math.max(0, width - 4))}${theme.fg(\"borderMuted\", \" \\u2502\")}`,\n\t\twidth,\n\t);\n}\n\nexport function fitLine(line: string, width: number): string {\n\tconst clipped = truncateToWidth(line, width, \"\");\n\treturn `${clipped}${\" \".repeat(Math.max(0, width - visibleWidth(clipped)))}`;\n}\n\nexport function clipLine(line: string, width: number): string {\n\treturn visibleWidth(line) <= width ? line : truncateToWidth(line, width, \"\");\n}\n"]}
|
|
@@ -3,31 +3,83 @@ import { theme } from "../theme/theme.js";
|
|
|
3
3
|
export function composeColumns(leftLines, leftWidth, rightLines, rightWidth, gapWidth, width) {
|
|
4
4
|
return Array.from({ length: Math.max(leftLines.length, rightLines.length) }, (_, index) => clipLine(`${fitLine(leftLines[index] ?? "", leftWidth)}${" ".repeat(gapWidth)}${fitLine(rightLines[index] ?? "", rightWidth)}`, width));
|
|
5
5
|
}
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Section rule for the control rail.
|
|
8
|
+
*
|
|
9
|
+
* Editorial treatment: the label sits flush left behind a single hairline tick
|
|
10
|
+
* and the rule runs out to the right edge, instead of a centered bold caption.
|
|
11
|
+
* Frames stay hairline so the accent colour is reserved for live values.
|
|
12
|
+
*/
|
|
13
|
+
export function sidebarRule(width, label, borderColor = "borderMuted") {
|
|
7
14
|
const bodyWidth = Math.max(0, width - 2);
|
|
8
15
|
const labelText = ` ${label} `;
|
|
9
|
-
const fill = Math.max(0, bodyWidth - visibleWidth(labelText));
|
|
10
|
-
|
|
11
|
-
const right = fill - left;
|
|
12
|
-
return clipLine(`${theme.fg(borderColor, "\u2502")}${theme.fg("borderMuted", "\u2500".repeat(left))}${theme.bold(theme.fg("accent", labelText))}${theme.fg("borderMuted", "\u2500".repeat(right))}${theme.fg(borderColor, "\u2502")}`, width);
|
|
16
|
+
const fill = Math.max(0, bodyWidth - visibleWidth(labelText) - 1);
|
|
17
|
+
return clipLine(`${theme.fg(borderColor, "\u2502")}${theme.fg("borderMuted", "\u2500")}${theme.fg("accent", labelText)}${theme.fg("borderMuted", "\u2500".repeat(fill))}${theme.fg(borderColor, "\u2502")}`, width);
|
|
13
18
|
}
|
|
14
|
-
|
|
19
|
+
/** Width of the right-aligned label gutter shared by every control-rail row. */
|
|
20
|
+
export const RAIL_LABEL_COLUMN = 8;
|
|
21
|
+
/**
|
|
22
|
+
* Right-aligned `label:` gutter cell.
|
|
23
|
+
*
|
|
24
|
+
* Labels are right-aligned so every value starts on the same column while the
|
|
25
|
+
* rendered text keeps exactly one space after the colon (`route: active`),
|
|
26
|
+
* which downstream reference-fidelity assertions depend on.
|
|
27
|
+
*/
|
|
28
|
+
export function labelCell(label, column = RAIL_LABEL_COLUMN) {
|
|
29
|
+
const text = `${label}:`;
|
|
30
|
+
const pad = Math.max(0, column + 1 - visibleWidth(text));
|
|
31
|
+
return `${" ".repeat(pad)}${theme.fg("muted", text)}`;
|
|
32
|
+
}
|
|
33
|
+
export function boxTop(width, label, borderColor = "borderMuted") {
|
|
15
34
|
const text = ` ${label} `;
|
|
16
35
|
const fillWidth = Math.max(0, width - visibleWidth("+") - visibleWidth(text) - visibleWidth("+"));
|
|
17
36
|
return clipLine(`${theme.fg(borderColor, "\u256d")}${theme.bold(theme.fg("accent", text))}${theme.fg(borderColor, "\u2500".repeat(fillWidth))}${theme.fg(borderColor, "\u256e")}`, width);
|
|
18
37
|
}
|
|
19
|
-
export function boxBottom(width, borderColor = "
|
|
38
|
+
export function boxBottom(width, borderColor = "borderMuted") {
|
|
20
39
|
return clipLine(theme.fg(borderColor, `\u2570${"\u2500".repeat(Math.max(0, width - 2))}\u256f`), width);
|
|
21
40
|
}
|
|
22
41
|
export function boxBlankLine(width) {
|
|
23
42
|
return boxTextLine(width, "");
|
|
24
43
|
}
|
|
44
|
+
/** Hairline rule inside a framed panel, inset from both frame edges. */
|
|
45
|
+
export function boxRuleLine(width, inset = 0) {
|
|
46
|
+
const innerWidth = Math.max(0, width - 4);
|
|
47
|
+
const ruleWidth = Math.max(0, innerWidth - inset * 2);
|
|
48
|
+
return boxTextLine(width, `${" ".repeat(inset)}${theme.fg("borderMuted", "\u2500".repeat(ruleWidth))}`);
|
|
49
|
+
}
|
|
25
50
|
export function boxCenteredLine(width, text, color) {
|
|
26
51
|
return boxTextLine(width, centerText(Math.max(0, width - 4), color ? theme.fg(color, text) : text));
|
|
27
52
|
}
|
|
28
53
|
export function boxTextLine(width, text, color) {
|
|
29
54
|
const body = color ? theme.fg(color, text) : text;
|
|
30
|
-
return clipLine(`${theme.fg("
|
|
55
|
+
return clipLine(`${theme.fg("borderMuted", "\u2502 ")}${fitLine(body, Math.max(0, width - 4))}${theme.fg("borderMuted", " \u2502")}`, width);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Pad a framed column with blank body rows so two side-by-side panels close on
|
|
59
|
+
* the same row. The leading and trailing frame lines are preserved.
|
|
60
|
+
*
|
|
61
|
+
* `"center"` splits the filler above and below the body, which turns leftover
|
|
62
|
+
* height into deliberate negative space instead of a hollow bottom half.
|
|
63
|
+
*/
|
|
64
|
+
export function padBoxColumn(lines, targetHeight, width, distribution = "bottom") {
|
|
65
|
+
if (lines.length === 0 || lines.length >= targetHeight)
|
|
66
|
+
return lines;
|
|
67
|
+
const top = lines[0];
|
|
68
|
+
const bottom = lines[lines.length - 1];
|
|
69
|
+
const body = lines.slice(1, -1);
|
|
70
|
+
const missing = targetHeight - lines.length;
|
|
71
|
+
const blank = () => boxTextLine(width, "");
|
|
72
|
+
if (distribution === "bottom") {
|
|
73
|
+
return [top, ...body, ...Array.from({ length: missing }, blank), bottom];
|
|
74
|
+
}
|
|
75
|
+
const above = Math.floor(missing / 2);
|
|
76
|
+
return [
|
|
77
|
+
top,
|
|
78
|
+
...Array.from({ length: above }, blank),
|
|
79
|
+
...body,
|
|
80
|
+
...Array.from({ length: missing - above }, blank),
|
|
81
|
+
bottom,
|
|
82
|
+
];
|
|
31
83
|
}
|
|
32
84
|
export function centerLine(width, text, color) {
|
|
33
85
|
return clipLine(centerText(width, color ? theme.fg(color, text) : text), width);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-panel-box.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel-box.ts"],"names":[],"mappings":"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","sourcesContent":["import { truncateToWidth, visibleWidth } from \"omk-tui\";\nimport { type ThemeColor, theme } from \"../theme/theme.ts\";\n\nexport function composeColumns(\n\tleftLines: string[],\n\tleftWidth: number,\n\trightLines: string[],\n\trightWidth: number,\n\tgapWidth: number,\n\twidth: number,\n): string[] {\n\treturn Array.from({ length: Math.max(leftLines.length, rightLines.length) }, (_, index) =>\n\t\tclipLine(\n\t\t\t`${fitLine(leftLines[index] ?? \"\", leftWidth)}${\" \".repeat(gapWidth)}${fitLine(rightLines[index] ?? \"\", rightWidth)}`,\n\t\t\twidth,\n\t\t),\n\t);\n}\n\nexport function sidebarRule(width: number, label: string, borderColor: ThemeColor = \"borderAccent\"): string {\n\tconst bodyWidth = Math.max(0, width - 2);\n\tconst labelText = ` ${label} `;\n\tconst fill = Math.max(0, bodyWidth - visibleWidth(labelText));\n\tconst left = Math.floor(fill / 2);\n\tconst right = fill - left;\n\treturn clipLine(\n\t\t`${theme.fg(borderColor, \"\\u2502\")}${theme.fg(\"borderMuted\", \"\\u2500\".repeat(left))}${theme.bold(theme.fg(\"accent\", labelText))}${theme.fg(\"borderMuted\", \"\\u2500\".repeat(right))}${theme.fg(borderColor, \"\\u2502\")}`,\n\t\twidth,\n\t);\n}\n\nexport function boxTop(width: number, label: string, borderColor: ThemeColor = \"borderAccent\"): string {\n\tconst text = ` ${label} `;\n\tconst fillWidth = Math.max(0, width - visibleWidth(\"+\") - visibleWidth(text) - visibleWidth(\"+\"));\n\treturn clipLine(\n\t\t`${theme.fg(borderColor, \"\\u256d\")}${theme.bold(theme.fg(\"accent\", text))}${theme.fg(borderColor, \"\\u2500\".repeat(fillWidth))}${theme.fg(borderColor, \"\\u256e\")}`,\n\t\twidth,\n\t);\n}\n\nexport function boxBottom(width: number, borderColor: ThemeColor = \"borderAccent\"): string {\n\treturn clipLine(theme.fg(borderColor, `\\u2570${\"\\u2500\".repeat(Math.max(0, width - 2))}\\u256f`), width);\n}\n\nexport function boxBlankLine(width: number): string {\n\treturn boxTextLine(width, \"\");\n}\n\nexport function boxCenteredLine(width: number, text: string, color?: ThemeColor): string {\n\treturn boxTextLine(width, centerText(Math.max(0, width - 4), color ? theme.fg(color, text) : text));\n}\n\nexport function boxTextLine(width: number, text: string, color?: ThemeColor): string {\n\tconst body = color ? theme.fg(color, text) : text;\n\treturn clipLine(\n\t\t`${theme.fg(\"borderAccent\", \"\\u2502 \")}${fitLine(body, Math.max(0, width - 4))}${theme.fg(\"borderAccent\", \" \\u2502\")}`,\n\t\twidth,\n\t);\n}\n\nexport function centerLine(width: number, text: string, color?: ThemeColor): string {\n\treturn clipLine(centerText(width, color ? theme.fg(color, text) : text), width);\n}\n\nexport function centerText(width: number, text: string): string {\n\tconst fitted = truncateToWidth(text, width, \"\");\n\tconst remaining = Math.max(0, width - visibleWidth(fitted));\n\treturn `${\" \".repeat(Math.floor(remaining / 2))}${fitted}${\" \".repeat(Math.ceil(remaining / 2))}`;\n}\n\nexport function divider(width: number, label: string, color: ThemeColor): string {\n\tconst fillWidth = Math.max(0, width - visibleWidth(\"+-- \") - visibleWidth(label) - visibleWidth(\" +\"));\n\treturn clipLine(\n\t\t`${theme.fg(\"border\", \"+-- \")}${theme.bold(theme.fg(color, label))}${theme.fg(\"border\", ` ${\"-\".repeat(fillWidth)}+`)}`,\n\t\twidth,\n\t);\n}\n\nexport function textLine(width: number, text: string, color?: ThemeColor): string {\n\tconst body = color ? theme.fg(color, text) : text;\n\treturn clipLine(\n\t\t`${theme.fg(\"borderMuted\", \"\\u2502 \")}${fitLine(body, Math.max(0, width - 4))}${theme.fg(\"borderMuted\", \" \\u2502\")}`,\n\t\twidth,\n\t);\n}\n\nexport function fitLine(line: string, width: number): string {\n\tconst clipped = truncateToWidth(line, width, \"\");\n\treturn `${clipped}${\" \".repeat(Math.max(0, width - visibleWidth(clipped)))}`;\n}\n\nexport function clipLine(line: string, width: number): string {\n\treturn visibleWidth(line) <= width ? line : truncateToWidth(line, width, \"\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"control-panel-box.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel-box.ts"],"names":[],"mappings":"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","sourcesContent":["import { truncateToWidth, visibleWidth } from \"omk-tui\";\nimport { type ThemeColor, theme } from \"../theme/theme.ts\";\n\nexport function composeColumns(\n\tleftLines: string[],\n\tleftWidth: number,\n\trightLines: string[],\n\trightWidth: number,\n\tgapWidth: number,\n\twidth: number,\n): string[] {\n\treturn Array.from({ length: Math.max(leftLines.length, rightLines.length) }, (_, index) =>\n\t\tclipLine(\n\t\t\t`${fitLine(leftLines[index] ?? \"\", leftWidth)}${\" \".repeat(gapWidth)}${fitLine(rightLines[index] ?? \"\", rightWidth)}`,\n\t\t\twidth,\n\t\t),\n\t);\n}\n\n/**\n * Section rule for the control rail.\n *\n * Editorial treatment: the label sits flush left behind a single hairline tick\n * and the rule runs out to the right edge, instead of a centered bold caption.\n * Frames stay hairline so the accent colour is reserved for live values.\n */\nexport function sidebarRule(width: number, label: string, borderColor: ThemeColor = \"borderMuted\"): string {\n\tconst bodyWidth = Math.max(0, width - 2);\n\tconst labelText = ` ${label} `;\n\tconst fill = Math.max(0, bodyWidth - visibleWidth(labelText) - 1);\n\treturn clipLine(\n\t\t`${theme.fg(borderColor, \"\\u2502\")}${theme.fg(\"borderMuted\", \"\\u2500\")}${theme.fg(\"accent\", labelText)}${theme.fg(\"borderMuted\", \"\\u2500\".repeat(fill))}${theme.fg(borderColor, \"\\u2502\")}`,\n\t\twidth,\n\t);\n}\n\n/** Width of the right-aligned label gutter shared by every control-rail row. */\nexport const RAIL_LABEL_COLUMN = 8;\n\n/**\n * Right-aligned `label:` gutter cell.\n *\n * Labels are right-aligned so every value starts on the same column while the\n * rendered text keeps exactly one space after the colon (`route: active`),\n * which downstream reference-fidelity assertions depend on.\n */\nexport function labelCell(label: string, column: number = RAIL_LABEL_COLUMN): string {\n\tconst text = `${label}:`;\n\tconst pad = Math.max(0, column + 1 - visibleWidth(text));\n\treturn `${\" \".repeat(pad)}${theme.fg(\"muted\", text)}`;\n}\n\nexport function boxTop(width: number, label: string, borderColor: ThemeColor = \"borderMuted\"): string {\n\tconst text = ` ${label} `;\n\tconst fillWidth = Math.max(0, width - visibleWidth(\"+\") - visibleWidth(text) - visibleWidth(\"+\"));\n\treturn clipLine(\n\t\t`${theme.fg(borderColor, \"\\u256d\")}${theme.bold(theme.fg(\"accent\", text))}${theme.fg(borderColor, \"\\u2500\".repeat(fillWidth))}${theme.fg(borderColor, \"\\u256e\")}`,\n\t\twidth,\n\t);\n}\n\nexport function boxBottom(width: number, borderColor: ThemeColor = \"borderMuted\"): string {\n\treturn clipLine(theme.fg(borderColor, `\\u2570${\"\\u2500\".repeat(Math.max(0, width - 2))}\\u256f`), width);\n}\n\nexport function boxBlankLine(width: number): string {\n\treturn boxTextLine(width, \"\");\n}\n\n/** Hairline rule inside a framed panel, inset from both frame edges. */\nexport function boxRuleLine(width: number, inset = 0): string {\n\tconst innerWidth = Math.max(0, width - 4);\n\tconst ruleWidth = Math.max(0, innerWidth - inset * 2);\n\treturn boxTextLine(width, `${\" \".repeat(inset)}${theme.fg(\"borderMuted\", \"\\u2500\".repeat(ruleWidth))}`);\n}\n\nexport function boxCenteredLine(width: number, text: string, color?: ThemeColor): string {\n\treturn boxTextLine(width, centerText(Math.max(0, width - 4), color ? theme.fg(color, text) : text));\n}\n\nexport function boxTextLine(width: number, text: string, color?: ThemeColor): string {\n\tconst body = color ? theme.fg(color, text) : text;\n\treturn clipLine(\n\t\t`${theme.fg(\"borderMuted\", \"\\u2502 \")}${fitLine(body, Math.max(0, width - 4))}${theme.fg(\"borderMuted\", \" \\u2502\")}`,\n\t\twidth,\n\t);\n}\n\n/**\n * Pad a framed column with blank body rows so two side-by-side panels close on\n * the same row. The leading and trailing frame lines are preserved.\n *\n * `\"center\"` splits the filler above and below the body, which turns leftover\n * height into deliberate negative space instead of a hollow bottom half.\n */\nexport function padBoxColumn(\n\tlines: string[],\n\ttargetHeight: number,\n\twidth: number,\n\tdistribution: \"bottom\" | \"center\" = \"bottom\",\n): string[] {\n\tif (lines.length === 0 || lines.length >= targetHeight) return lines;\n\tconst top = lines[0] as string;\n\tconst bottom = lines[lines.length - 1] as string;\n\tconst body = lines.slice(1, -1);\n\tconst missing = targetHeight - lines.length;\n\tconst blank = () => boxTextLine(width, \"\");\n\tif (distribution === \"bottom\") {\n\t\treturn [top, ...body, ...Array.from({ length: missing }, blank), bottom];\n\t}\n\tconst above = Math.floor(missing / 2);\n\treturn [\n\t\ttop,\n\t\t...Array.from({ length: above }, blank),\n\t\t...body,\n\t\t...Array.from({ length: missing - above }, blank),\n\t\tbottom,\n\t];\n}\n\nexport function centerLine(width: number, text: string, color?: ThemeColor): string {\n\treturn clipLine(centerText(width, color ? theme.fg(color, text) : text), width);\n}\n\nexport function centerText(width: number, text: string): string {\n\tconst fitted = truncateToWidth(text, width, \"\");\n\tconst remaining = Math.max(0, width - visibleWidth(fitted));\n\treturn `${\" \".repeat(Math.floor(remaining / 2))}${fitted}${\" \".repeat(Math.ceil(remaining / 2))}`;\n}\n\nexport function divider(width: number, label: string, color: ThemeColor): string {\n\tconst fillWidth = Math.max(0, width - visibleWidth(\"+-- \") - visibleWidth(label) - visibleWidth(\" +\"));\n\treturn clipLine(\n\t\t`${theme.fg(\"border\", \"+-- \")}${theme.bold(theme.fg(color, label))}${theme.fg(\"border\", ` ${\"-\".repeat(fillWidth)}+`)}`,\n\t\twidth,\n\t);\n}\n\nexport function textLine(width: number, text: string, color?: ThemeColor): string {\n\tconst body = color ? theme.fg(color, text) : text;\n\treturn clipLine(\n\t\t`${theme.fg(\"borderMuted\", \"\\u2502 \")}${fitLine(body, Math.max(0, width - 4))}${theme.fg(\"borderMuted\", \" \\u2502\")}`,\n\t\twidth,\n\t);\n}\n\nexport function fitLine(line: string, width: number): string {\n\tconst clipped = truncateToWidth(line, width, \"\");\n\treturn `${clipped}${\" \".repeat(Math.max(0, width - visibleWidth(clipped)))}`;\n}\n\nexport function clipLine(line: string, width: number): string {\n\treturn visibleWidth(line) <= width ? line : truncateToWidth(line, width, \"\");\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-panel-layout.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel-layout.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AACjF,OAAO,EAAkB,KAAK,SAAS,EAA0B,MAAM,6BAA6B,CAAC;AAgBrG,eAAO,MAAM,uBAAuB,UAQnC,CAAC;AAEF,eAAO,MAAM,+BAA+B,MAAM,CAAC;AACnD,eAAO,MAAM,4BAA4B,QAAsC,CAAC;AAChF,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAC9C,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAKzC,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,EAAE,MAAM,MAAM,CAAC;IAClC,oBAAoB,EAAE,MAAM,MAAM,CAAC;IACnC,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,UAAU,EAAE,MAAM,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,0BAA0B,CAAC;CAClD;AAED,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,sBAAsB,CAAC;IAChD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,mBAAmB,EAC5B,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EAAE,EACtB,SAAS,SAAI,GACX,MAAM,EAAE,CAKV;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAGjG","sourcesContent":["import { truncateToWidth, visibleWidth } from \"omk-tui\";\nimport type { PiPackageIntakeSummary } from \"../../../core/pi-package-intake.ts\";\nimport { nextActiveTodo, type TodoState, summary as todoSummary } from \"../../../core/todo-state.ts\";\nimport { type ThemeColor, theme } from \"../theme/theme.ts\";\nimport {\n\tboxBottom,\n\tboxCenteredLine,\n\tboxTextLine,\n\tboxTop,\n\tcenterLine,\n\tcomposeColumns,\n\tdivider,\n\tfitLine,\n\tsidebarRule,\n\ttextLine,\n} from \"./control-panel-box.ts\";\nimport { SPARKLE_ROW_BOTTOM, SPARKLE_ROW_TOP, sparkleRow } from \"./control-panel-sparkles.ts\";\n\nexport const CONTROL_PANEL_ASCII_ART = [\n\t\" ███████ ██ ██ ██ ██\",\n\t\"██ ██ ███ ███ ██ ██ \",\n\t\"██ ██ ████ ████ ██ ██ \",\n\t\"██ ██ ██ ██ ██ ██ █████ \",\n\t\"██ ██ ██ ███ ██ ██ ██ \",\n\t\"██ ██ ██ █ ██ ██ ██ \",\n\t\" ███████ ██ ██ ██ ██\",\n];\nconst META_WIDTH = 31;\nexport const CONTROL_PANEL_OVERLAY_MIN_WIDTH = 112;\nexport const CONTROL_PANEL_DECK_MIN_WIDTH = CONTROL_PANEL_OVERLAY_MIN_WIDTH + 1;\nexport const CONTROL_PANEL_SIDEBAR_WIDTH = 38;\nexport const CONTROL_PANEL_GAP_WIDTH = 2;\nconst CONTEXT_METER_CELLS = 12;\nconst SPARKLINE_WIDTH = 16;\nconst SPARKLINE_GLYPHS = \"▁▂▃▄▅▆▇█\";\n\nexport interface ControlPanelContent {\n\tappName: string;\n\tversion: string;\n\tcompactInstructions: () => string;\n\texpandedInstructions: () => string;\n\tcompactOnboarding: () => string;\n\tonboarding: () => string;\n\tstatusSnapshot?: () => ControlPanelStatusSnapshot;\n}\n\nexport interface ControlPanelStatusSnapshot {\n\treadonly modelId?: string;\n\treadonly modelProvider?: string;\n\treadonly thinkingLevel?: string;\n\treadonly contextPercent?: number | null;\n\treadonly contextWindowTokens?: number;\n\treadonly contextTokens?: number | null;\n\treadonly headroomStatus?: string;\n\treadonly optimizerPolicy?: string;\n\treadonly skillCount?: number;\n\treadonly mcpCount?: number;\n\treadonly packageIntake?: PiPackageIntakeSummary;\n\treadonly cwdLabel?: string;\n\treadonly gitBranch?: string | null;\n\treadonly todoState?: TodoState;\n\treadonly runtimeState?: string;\n\treadonly routeState?: string;\n\treadonly evidenceState?: string;\n\treadonly controlState?: string;\n\treadonly dagOrchestrationState?: string;\n\treadonly ansiColorState?: string;\n\treadonly startupState?: string;\n\treadonly linkState?: string;\n\treadonly sidebarState?: string;\n}\n\nexport function renderControlPanelLayout(\n\tcontent: ControlPanelContent,\n\texpanded: boolean,\n\twidth: number,\n\tbannerFrame?: string[],\n\tsparkleMs = 0,\n): string[] {\n\tif (width <= 0) return [];\n\treturn expanded\n\t\t? renderExpanded(content, width, bannerFrame, sparkleMs)\n\t\t: renderCompact(content, width, bannerFrame, sparkleMs);\n}\n\nexport function renderControlPanelRightPane(content: ControlPanelContent, width: number): string[] {\n\tif (width <= 0) return [];\n\treturn sidebarPanel(content, width);\n}\n\nfunction renderCompact(content: ControlPanelContent, width: number, bannerFrame?: string[], sparkleMs = 0): string[] {\n\tif (width >= CONTROL_PANEL_DECK_MIN_WIDTH) {\n\t\tconst deck = renderDeck(content, width, bannerFrame, sparkleMs);\n\t\tif (deck.length > 0) return deck;\n\t}\n\treturn [\n\t\tdivider(width, \"OMK//CONTROL PANEL\", \"accent\"),\n\t\tstatusLine(content, width),\n\t\ttextLine(width, content.compactInstructions()),\n\t\ttextLine(width, content.compactOnboarding(), \"dim\"),\n\t];\n}\n\nfunction renderDeck(content: ControlPanelContent, width: number, bannerFrame?: string[], sparkleMs = 0): string[] {\n\tconst { leftWidth, sidebarWidth } = deckWidths(width);\n\tif (leftWidth < 72) return [];\n\tconst lines = composeColumns(\n\t\theroPanel(content, leftWidth, bannerFrame, sparkleMs),\n\t\tleftWidth,\n\t\tsidebarPanel(content, sidebarWidth),\n\t\tsidebarWidth,\n\t\tCONTROL_PANEL_GAP_WIDTH,\n\t\twidth,\n\t);\n\tlines.push(controlStripLine(content, width));\n\tlines.push(centerLine(width, content.compactOnboarding(), \"dim\"));\n\treturn lines;\n}\n\nfunction deckWidths(width: number): { leftWidth: number; sidebarWidth: number } {\n\tconst sidebarWidth = Math.min(CONTROL_PANEL_SIDEBAR_WIDTH, Math.max(34, Math.floor(width * 0.28)));\n\treturn { leftWidth: width - CONTROL_PANEL_GAP_WIDTH - sidebarWidth, sidebarWidth };\n}\n\nfunction heroPanel(content: ControlPanelContent, width: number, bannerFrame?: string[], sparkleMs = 0): string[] {\n\tconst snapshot = statusSnapshot(content);\n\tconst innerWidth = Math.max(0, width - 4);\n\treturn [\n\t\tboxTop(width, `omk v${content.version} · OMK://CONTROL`),\n\t\tboxCenteredLine(width, theme.bold(theme.fg(\"accent\", \"OMK\"))),\n\t\tboxCenteredLine(width, theme.fg(\"muted\", \"route · verify · loop · control\")),\n\t\tboxTextLine(width, sparkleRow(innerWidth, SPARKLE_ROW_TOP, sparkleMs)),\n\t\t...(bannerFrame ?? CONTROL_PANEL_ASCII_ART).map((line, index) => {\n\t\t\tif (bannerFrame) return boxCenteredLine(width, line);\n\t\t\tconst color: ThemeColor = index < 2 ? \"mdCode\" : index < 4 ? \"accent\" : index < 6 ? \"success\" : \"warning\";\n\t\t\treturn boxCenteredLine(width, theme.fg(color, line));\n\t\t}),\n\t\tboxTextLine(width, sparkleRow(innerWidth, SPARKLE_ROW_BOTTOM, sparkleMs)),\n\t\tboxCenteredLine(\n\t\t\twidth,\n\t\t\t`${theme.fg(\"warning\", \"●\")} ${theme.bold(\"OMK\")} / ${theme.fg(\"success\", heroModelLabel(snapshot))}`,\n\t\t),\n\t\tboxCenteredLine(\n\t\t\twidth,\n\t\t\t`${theme.fg(\"mdCode\", \"◇\")} omk-control · ${theme.fg(\"accent\", \"route\")} · ${theme.fg(\"warning\", \"verify\")} · ${theme.fg(\"success\", \"loop\")} · ${theme.fg(\"mdCode\", \"control\")}`,\n\t\t),\n\t\tboxBottom(width),\n\t];\n}\n\nfunction heroModelLabel(snapshot: ControlPanelStatusSnapshot): string {\n\tif (!snapshot.modelId) return \"no-model\";\n\tconst think = snapshot.thinkingLevel;\n\treturn think && think !== \"off\" ? `${snapshot.modelId}:${think}` : snapshot.modelId;\n}\n\nfunction sidebarPanel(content: ControlPanelContent, width: number): string[] {\n\tconst snapshot = statusSnapshot(content);\n\tconst headroomLabel = snapshot.headroomStatus ?? snapshot.optimizerPolicy ?? \"unknown\";\n\tconst mcpCount = snapshot.mcpCount;\n\tconst skillCount = snapshot.skillCount;\n\tconst packageIntakeLabel = packageIntakeStatusLabel(snapshot.packageIntake);\n\tconst contextMeter = contextMeterLabel(snapshot);\n\tconst activitySparkline = activitySparklineLabel(snapshot);\n\treturn [\n\t\tsidebarTabs(width),\n\t\tboxCenteredLine(width, theme.bold(theme.fg(\"accent\", \"OMK://CONTROL\"))),\n\t\tboxCenteredLine(width, theme.bold(theme.fg(\"warning\", \"CYBERPUNK OPS CORE\"))),\n\t\tboxCenteredLine(width, `${theme.fg(\"mdCode\", \"MATRIX RAIN\")} // ${theme.fg(\"success\", \"NEON GRID ONLINE\")}`),\n\t\tboxCenteredLine(width, theme.fg(\"text\", \"NIGHT-CITY-MATRIX-V3\")),\n\t\tsidebarRule(width, \"STATUS\", \"accent\"),\n\t\tboxTextLine(width, `${theme.fg(\"muted\", \"state:\")} ${coloredStatus(snapshot.runtimeState ?? \"unknown\")}`),\n\t\tboxTextLine(width, `${theme.fg(\"muted\", \"route:\")} ${coloredStatus(snapshot.routeState ?? \"unknown\")}`),\n\t\tboxTextLine(width, `${theme.fg(\"muted\", \"evidence:\")} ${coloredStatus(snapshot.evidenceState ?? \"unknown\")}`),\n\t\tsidebarRule(width, \"TODO\", \"accent\"),\n\t\t...todoSidebarLines(snapshot.todoState, width),\n\t\tsidebarRule(width, \"SESSION\", \"accent\"),\n\t\tsemanticBoxTextLine(width, \"cwd\", snapshot.cwdLabel ?? \"?\", \"end\"),\n\t\tsemanticBoxTextLine(width, \"git\", snapshot.gitBranch ?? \"?\", \"start\"),\n\t\tsidebarRule(width, \"MODEL / CTX\", \"accent\"),\n\t\tsemanticBoxTextLine(width, \"model\", modelStatusLabel(snapshot), \"end\"),\n\t\tsemanticBoxTextLine(width, \"think\", snapshot.thinkingLevel ?? \"off\", \"start\"),\n\t\tboxTextLine(\n\t\t\twidth,\n\t\t\t`${theme.fg(\"muted\", \"ctx:\")} ${theme.fg(statusColor(snapshot.contextPercent === undefined || snapshot.contextPercent === null ? \"unknown\" : \"ready\"), contextStatusLabel(snapshot))}`,\n\t\t),\n\t\tboxTextLine(width, `${theme.fg(\"muted\", \"meter:\")} ${contextMeter}`),\n\t\tboxTextLine(width, `${theme.fg(\"muted\", \"pulse:\")} ${activitySparkline}`),\n\t\tsidebarRule(width, \"RUNTIME / MCP / SKILLS\", \"accent\"),\n\t\tsemanticBoxTextLine(width, \"headroom\", headroomLabel, \"end\"),\n\t\tsemanticBoxTextLine(width, \"omk\", snapshot.dagOrchestrationState ?? \"unknown\", \"end\"),\n\t\tsemanticBoxTextLine(width, \"sidebar\", snapshot.sidebarState ?? \"unknown\", \"start\"),\n\t\tboxTextLine(width, `${theme.fg(\"muted\", \"res:\")} MCP:${mcpCount ?? \"?\"} skills:${skillCount ?? \"?\"}`),\n\t\tsemanticBoxTextLine(width, \"pkg\", packageIntakeLabel, \"end\"),\n\t\tsidebarRule(width, \"CONTROL\", \"accent\"),\n\t\tboxTextLine(width, `${theme.fg(\"muted\", \"route:\")} ${coloredStatus(snapshot.routeState ?? \"unknown\")}`),\n\t\tboxTextLine(width, `${theme.fg(\"muted\", \"verify:\")} ${coloredStatus(snapshot.evidenceState ?? \"unknown\")}`),\n\t\tboxTextLine(width, `${theme.fg(\"muted\", \"control:\")} ${coloredStatus(snapshot.controlState ?? \"unknown\")}`),\n\t\tboxBottom(width, \"accent\"),\n\t];\n}\n\nfunction semanticBoxTextLine(\n\twidth: number,\n\tlabel: string,\n\tvalue: string,\n\tpreserve: \"start\" | \"middle\" | \"end\",\n): string {\n\tconst prefix = `${theme.fg(\"muted\", `${label}:`)} `;\n\tconst availableWidth = Math.max(0, width - 4 - visibleWidth(prefix));\n\treturn boxTextLine(width, `${prefix}${semanticTruncate(value, availableWidth, preserve)}`);\n}\n\nfunction semanticTruncate(value: string, maxWidth: number, preserve: \"start\" | \"middle\" | \"end\"): string {\n\tif (visibleWidth(value) <= maxWidth) return value;\n\tif (maxWidth <= 0) return \"\";\n\tif (preserve === \"start\") return truncateToWidth(value, maxWidth, \"…\");\n\tconst ellipsis = \"…\";\n\tconst targetWidth = maxWidth - visibleWidth(ellipsis);\n\tif (targetWidth <= 0) return truncateToWidth(ellipsis, maxWidth, \"\");\n\tif (preserve === \"middle\") {\n\t\tconst headWidth = Math.max(1, Math.floor(targetWidth / 2));\n\t\tconst tailWidth = Math.max(1, targetWidth - headWidth);\n\t\treturn `${takeStart(value, headWidth)}${ellipsis}${takeEnd(value, tailWidth)}`;\n\t}\n\treturn `${ellipsis}${takeEnd(value, targetWidth)}`;\n}\n\nfunction takeStart(value: string, maxWidth: number): string {\n\tlet prefix = \"\";\n\tfor (const char of Array.from(value)) {\n\t\tif (visibleWidth(prefix + char) > maxWidth) break;\n\t\tprefix += char;\n\t}\n\treturn prefix;\n}\n\nfunction takeEnd(value: string, maxWidth: number): string {\n\tlet suffix = \"\";\n\tfor (const char of Array.from(value).reverse()) {\n\t\tif (visibleWidth(char + suffix) > maxWidth) break;\n\t\tsuffix = char + suffix;\n\t}\n\treturn suffix;\n}\n\nfunction statusToken(label: string, value: string | undefined): string {\n\tconst state = value ?? \"unknown\";\n\treturn theme.fg(statusColor(state), `${label}:${state.toUpperCase()}`);\n}\n\nfunction coloredStatus(value: string): string {\n\treturn theme.fg(statusColor(value), value);\n}\n\nfunction statusColor(value: string): ThemeColor {\n\tconst normalized = value.toLowerCase();\n\tif ([\"ready\", \"on\", \"active\", \"available\", \"linked\", \"tracking\", \"pinned\"].includes(normalized)) return \"success\";\n\tif ([\"degraded\", \"limited\", \"blocked\", \"off\"].includes(normalized)) return \"warning\";\n\treturn \"muted\";\n}\n\nfunction todoSidebarLines(state: TodoState | undefined, width: number): string[] {\n\tif (!state || state.items.length === 0) {\n\t\treturn [\n\t\t\tboxTextLine(width, `${theme.fg(\"muted\", \"todo:\")} empty`),\n\t\t\tboxTextLine(width, `${theme.fg(\"muted\", \"next:\")} no active todos`),\n\t\t];\n\t}\n\tconst counts = todoSummary(state);\n\tconst next = nextActiveTodo(state);\n\treturn [\n\t\tboxTextLine(width, `${theme.fg(\"muted\", \"todo:\")} ${counts.done}/${counts.total} done`),\n\t\tsemanticBoxTextLine(width, \"next\", next?.label ?? \"complete\", \"middle\"),\n\t];\n}\n\nfunction packageIntakeStatusLabel(summary: PiPackageIntakeSummary | undefined): string {\n\tif (!summary) return \"ports:pending\";\n\tconst ready =\n\t\tsummary.acceptedNative + summary.acceptedReference + summary.acceptedMeasurement + summary.acceptedAdvisory;\n\tconst review = summary.deferred + summary.reject;\n\treturn `ports:${ready}/${summary.total} review:${review} block:${summary.hardForkBlocked}`;\n}\n\nfunction renderExpanded(content: ControlPanelContent, width: number, bannerFrame?: string[], sparkleMs = 0): string[] {\n\tif (width >= CONTROL_PANEL_DECK_MIN_WIDTH) {\n\t\tconst { leftWidth, sidebarWidth } = deckWidths(width);\n\t\tconst resourceLines = [\"\", ...content.onboarding().split(\"\\n\")];\n\t\tconst rightRail = blankSidebarRail(sidebarWidth, resourceLines.length);\n\t\tconst lines = [...renderDeck(content, width, bannerFrame, sparkleMs)];\n\t\tlines.push(...composeColumns(resourceLines, leftWidth, rightRail, sidebarWidth, CONTROL_PANEL_GAP_WIDTH, width));\n\t\treturn lines;\n\t}\n\n\tconst lines = [divider(width, \"OMK//CONTROL PANEL\", \"accent\"), statusLine(content, width)];\n\tif (width >= 32) lines.push(...brandLines(content, width, bannerFrame));\n\tlines.push(divider(width, \"SYSTEM MAP\", \"mdHeading\"));\n\tfor (const instruction of content.expandedInstructions().split(\"\\n\")) lines.push(textLine(width, instruction));\n\tlines.push(divider(width, \"STARTUP LINK\", \"success\"));\n\tfor (const onboardingLine of content.onboarding().split(\"\\n\")) lines.push(textLine(width, onboardingLine, \"dim\"));\n\tlines.push(divider(width, \"END\", \"borderMuted\"));\n\treturn lines;\n}\n\nfunction blankSidebarRail(width: number, lineCount: number): string[] {\n\treturn Array.from({ length: lineCount }, () => boxTextLine(width, \"\"));\n}\n\nfunction statusSnapshot(content: ControlPanelContent): ControlPanelStatusSnapshot {\n\treturn content.statusSnapshot?.() ?? {};\n}\n\nfunction modelStatusLabel(snapshot: ControlPanelStatusSnapshot): string {\n\tif (!snapshot.modelId) return \"no-model\";\n\treturn snapshot.modelProvider ? `${snapshot.modelProvider}/${snapshot.modelId}` : snapshot.modelId;\n}\n\nfunction contextStatusLabel(snapshot: ControlPanelStatusSnapshot): string {\n\tconst windowTokens = snapshot.contextWindowTokens ?? 0;\n\tconst windowLabel = windowTokens > 0 ? formatTokens(windowTokens) : \"?\";\n\treturn snapshot.contextPercent === null || snapshot.contextPercent === undefined\n\t\t? `?/${windowLabel}`\n\t\t: `${snapshot.contextPercent.toFixed(1)}%/${windowLabel}`;\n}\n\nfunction contextMeterLabel(snapshot: ControlPanelStatusSnapshot): string {\n\tconst percent = normalizePercent(snapshot.contextPercent);\n\tif (percent === undefined) {\n\t\treturn `${theme.fg(\"borderMuted\", \"░\".repeat(CONTEXT_METER_CELLS))} ${theme.fg(\"muted\", \"??%\")}`;\n\t}\n\tconst filled = Math.max(0, Math.min(CONTEXT_METER_CELLS, Math.round((percent / 100) * CONTEXT_METER_CELLS)));\n\tconst color: ThemeColor = percent >= 85 ? \"warning\" : percent >= 65 ? \"mdCode\" : \"success\";\n\treturn `${theme.fg(color, \"█\".repeat(filled))}${theme.fg(\"borderMuted\", \"░\".repeat(CONTEXT_METER_CELLS - filled))} ${theme.fg(color, `${Math.round(percent)}%`)}`;\n}\n\nfunction activitySparklineLabel(snapshot: ControlPanelStatusSnapshot): string {\n\tconst seed = hashSnapshot(snapshot);\n\tconst glyphs = Array.from(SPARKLINE_GLYPHS);\n\tconst parts: string[] = [];\n\tfor (let index = 0; index < SPARKLINE_WIDTH; index++) {\n\t\tconst glyph = glyphs[(seed + index * 5 + index * index) % glyphs.length] ?? \"▁\";\n\t\tconst color: ThemeColor = index % 5 === 0 ? \"accent\" : index % 3 === 0 ? \"warning\" : \"success\";\n\t\tparts.push(theme.fg(color, glyph));\n\t}\n\treturn parts.join(\"\");\n}\n\nfunction normalizePercent(value: number | null | undefined): number | undefined {\n\tif (value === null || value === undefined || !Number.isFinite(value)) return undefined;\n\tif (value < 0) return 0;\n\tif (value > 100) return 100;\n\treturn value;\n}\n\nfunction hashSnapshot(snapshot: ControlPanelStatusSnapshot): number {\n\tconst source = [\n\t\tsnapshot.modelProvider,\n\t\tsnapshot.modelId,\n\t\tsnapshot.contextPercent?.toFixed(1),\n\t\tsnapshot.contextTokens,\n\t\tsnapshot.contextWindowTokens,\n\t\tsnapshot.cwdLabel,\n\t\tsnapshot.gitBranch,\n\t].join(\"|\");\n\tlet hash = 2166136261;\n\tfor (let index = 0; index < source.length; index++) {\n\t\thash ^= source.charCodeAt(index);\n\t\thash = Math.imul(hash, 16777619);\n\t}\n\treturn hash >>> 0;\n}\n\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\nfunction sidebarTabs(width: number): string {\n\treturn boxTextLine(\n\t\twidth,\n\t\tfitLine(\n\t\t\t`${theme.bold(theme.fg(\"accent\", \"1:CONTROL\"))} ${theme.fg(\"muted\", \"2:HISTORY\")}`,\n\t\t\tMath.max(0, width - 4),\n\t\t),\n\t);\n}\n\nfunction controlStripLine(content: ControlPanelContent, width: number): string {\n\treturn centerLine(\n\t\twidth,\n\t\t`${theme.bold(theme.fg(\"accent\", \"OMK://CONTROL READ\"))} ${theme.fg(\"accent\", \"route/verify/loop/control\")} · ${content.compactInstructions()}`,\n\t);\n}\n\nfunction brandLines(content: ControlPanelContent, width: number, bannerFrame?: string[]): string[] {\n\tconst art = bannerFrame ?? CONTROL_PANEL_ASCII_ART;\n\tif (bannerFrame) return art.map((line) => textLine(width, line));\n\tconst leftWidth = Math.max(...art.map((line) => visibleWidth(line)));\n\tconst minWideWidth = visibleWidth(\"| \") + leftWidth + visibleWidth(\" | \") + META_WIDTH;\n\tif (width < minWideWidth) return art.map((line) => textLine(width, theme.fg(\"accent\", line)));\n\tconst snapshot = statusSnapshot(content);\n\treturn art.map((line, index) =>\n\t\ttextLine(\n\t\t\twidth,\n\t\t\t`${fitLine(theme.fg(\"accent\", line), leftWidth)}${theme.fg(\"borderMuted\", \" | \")}${metadataLines(snapshot)[index] ?? \"\"}`,\n\t\t),\n\t);\n}\n\nfunction metadataLines(snapshot: ControlPanelStatusSnapshot): string[] {\n\tconst rawThemeName = theme.name ?? \"live\";\n\tconst themeName = (rawThemeName.startsWith(\"omk-\") ? rawThemeName.slice(4) : rawThemeName).toUpperCase();\n\treturn [\n\t\t`${theme.fg(\"mdCode\", \"PANEL\")} ${coloredStatus(snapshot.runtimeState ?? \"unknown\")}`,\n\t\t`${theme.fg(\"mdCode\", \"THEME\")} ${theme.fg(\"accent\", themeName)}`,\n\t\t`${theme.fg(\"mdCode\", \"STARTUP\")} ${coloredStatus(snapshot.startupState ?? \"unknown\")}`,\n\t\t`${theme.fg(\"mdCode\", \"LINK\")} ${coloredStatus(snapshot.linkState ?? \"unknown\")}`,\n\t];\n}\n\nfunction statusLine(content: ControlPanelContent, width: number): string {\n\tconst snapshot = statusSnapshot(content);\n\tconst segments = [\n\t\ttheme.bold(theme.fg(\"accent\", `${content.appName.toUpperCase()} v${content.version}`)),\n\t\tstatusToken(\"CORE\", snapshot.runtimeState),\n\t\tstatusToken(\"ANSI\", snapshot.ansiColorState),\n\t\tstatusToken(\"STARTUP\", snapshot.startupState),\n\t\tstatusToken(\"LINK\", snapshot.linkState),\n\t\ttheme.fg(\"muted\", \"THEME:LIVE\"),\n\t];\n\treturn textLine(width, segments.join(theme.fg(\"borderMuted\", \" | \")));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"control-panel-layout.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel-layout.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AACjF,OAAO,EAAkB,KAAK,SAAS,EAA0B,MAAM,6BAA6B,CAAC;AAmBrG,eAAO,MAAM,uBAAuB,UAQnC,CAAC;AAEF,eAAO,MAAM,+BAA+B,MAAM,CAAC;AACnD,eAAO,MAAM,4BAA4B,QAAsC,CAAC;AAChF,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAC9C,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAKzC,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,EAAE,MAAM,MAAM,CAAC;IAClC,oBAAoB,EAAE,MAAM,MAAM,CAAC;IACnC,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,UAAU,EAAE,MAAM,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,0BAA0B,CAAC;CAClD;AAED,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,sBAAsB,CAAC;IAChD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,mBAAmB,EAC5B,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EAAE,EACtB,SAAS,SAAI,GACX,MAAM,EAAE,CAKV;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAGjG","sourcesContent":["import { truncateToWidth, visibleWidth } from \"omk-tui\";\nimport type { PiPackageIntakeSummary } from \"../../../core/pi-package-intake.ts\";\nimport { nextActiveTodo, type TodoState, summary as todoSummary } from \"../../../core/todo-state.ts\";\nimport { type ThemeColor, theme } from \"../theme/theme.ts\";\nimport {\n\tboxBottom,\n\tboxCenteredLine,\n\tboxRuleLine,\n\tboxTextLine,\n\tboxTop,\n\tcenterLine,\n\tcomposeColumns,\n\tdivider,\n\tfitLine,\n\tlabelCell,\n\tpadBoxColumn,\n\tsidebarRule,\n\ttextLine,\n} from \"./control-panel-box.ts\";\nimport { SPARKLE_ROW_BOTTOM, SPARKLE_ROW_TOP, sparkleRow } from \"./control-panel-sparkles.ts\";\n\nexport const CONTROL_PANEL_ASCII_ART = [\n\t\" ███████ ██ ██ ██ ██\",\n\t\"██ ██ ███ ███ ██ ██ \",\n\t\"██ ██ ████ ████ ██ ██ \",\n\t\"██ ██ ██ ██ ██ ██ █████ \",\n\t\"██ ██ ██ ███ ██ ██ ██ \",\n\t\"██ ██ ██ █ ██ ██ ██ \",\n\t\" ███████ ██ ██ ██ ██\",\n];\nconst META_WIDTH = 31;\nexport const CONTROL_PANEL_OVERLAY_MIN_WIDTH = 112;\nexport const CONTROL_PANEL_DECK_MIN_WIDTH = CONTROL_PANEL_OVERLAY_MIN_WIDTH + 1;\nexport const CONTROL_PANEL_SIDEBAR_WIDTH = 38;\nexport const CONTROL_PANEL_GAP_WIDTH = 2;\nconst CONTEXT_METER_CELLS = 12;\nconst SPARKLINE_WIDTH = 16;\nconst SPARKLINE_GLYPHS = \"▁▂▃▄▅▆▇█\";\n\nexport interface ControlPanelContent {\n\tappName: string;\n\tversion: string;\n\tcompactInstructions: () => string;\n\texpandedInstructions: () => string;\n\tcompactOnboarding: () => string;\n\tonboarding: () => string;\n\tstatusSnapshot?: () => ControlPanelStatusSnapshot;\n}\n\nexport interface ControlPanelStatusSnapshot {\n\treadonly modelId?: string;\n\treadonly modelProvider?: string;\n\treadonly thinkingLevel?: string;\n\treadonly contextPercent?: number | null;\n\treadonly contextWindowTokens?: number;\n\treadonly contextTokens?: number | null;\n\treadonly headroomStatus?: string;\n\treadonly optimizerPolicy?: string;\n\treadonly skillCount?: number;\n\treadonly mcpCount?: number;\n\treadonly packageIntake?: PiPackageIntakeSummary;\n\treadonly cwdLabel?: string;\n\treadonly gitBranch?: string | null;\n\treadonly todoState?: TodoState;\n\treadonly runtimeState?: string;\n\treadonly routeState?: string;\n\treadonly evidenceState?: string;\n\treadonly controlState?: string;\n\treadonly dagOrchestrationState?: string;\n\treadonly ansiColorState?: string;\n\treadonly startupState?: string;\n\treadonly linkState?: string;\n\treadonly sidebarState?: string;\n}\n\nexport function renderControlPanelLayout(\n\tcontent: ControlPanelContent,\n\texpanded: boolean,\n\twidth: number,\n\tbannerFrame?: string[],\n\tsparkleMs = 0,\n): string[] {\n\tif (width <= 0) return [];\n\treturn expanded\n\t\t? renderExpanded(content, width, bannerFrame, sparkleMs)\n\t\t: renderCompact(content, width, bannerFrame, sparkleMs);\n}\n\nexport function renderControlPanelRightPane(content: ControlPanelContent, width: number): string[] {\n\tif (width <= 0) return [];\n\treturn sidebarPanel(content, width);\n}\n\nfunction renderCompact(content: ControlPanelContent, width: number, bannerFrame?: string[], sparkleMs = 0): string[] {\n\tif (width >= CONTROL_PANEL_DECK_MIN_WIDTH) {\n\t\tconst deck = renderDeck(content, width, bannerFrame, sparkleMs);\n\t\tif (deck.length > 0) return deck;\n\t}\n\treturn [\n\t\tdivider(width, \"OMK//CONTROL PANEL\", \"accent\"),\n\t\tstatusLine(content, width),\n\t\ttextLine(width, content.compactInstructions()),\n\t\ttextLine(width, content.compactOnboarding(), \"dim\"),\n\t];\n}\n\nfunction renderDeck(content: ControlPanelContent, width: number, bannerFrame?: string[], sparkleMs = 0): string[] {\n\tconst { leftWidth, sidebarWidth } = deckWidths(width);\n\tif (leftWidth < 72) return [];\n\tconst hero = heroPanel(content, leftWidth, bannerFrame, sparkleMs);\n\tconst rail = sidebarPanel(content, sidebarWidth);\n\t// Both framed columns close on the same row so the deck reads as one block\n\t// instead of a short hero beside a long ragged rail.\n\tconst deckHeight = Math.max(hero.length, rail.length);\n\tconst lines = composeColumns(\n\t\tpadBoxColumn(hero, deckHeight, leftWidth, \"center\"),\n\t\tleftWidth,\n\t\tpadBoxColumn(rail, deckHeight, sidebarWidth),\n\t\tsidebarWidth,\n\t\tCONTROL_PANEL_GAP_WIDTH,\n\t\twidth,\n\t);\n\tlines.push(controlStripLine(content, width));\n\tlines.push(centerLine(width, content.compactOnboarding(), \"dim\"));\n\treturn lines;\n}\n\nfunction deckWidths(width: number): { leftWidth: number; sidebarWidth: number } {\n\tconst sidebarWidth = Math.min(CONTROL_PANEL_SIDEBAR_WIDTH, Math.max(34, Math.floor(width * 0.28)));\n\treturn { leftWidth: width - CONTROL_PANEL_GAP_WIDTH - sidebarWidth, sidebarWidth };\n}\n\nfunction heroPanel(content: ControlPanelContent, width: number, bannerFrame?: string[], sparkleMs = 0): string[] {\n\tconst snapshot = statusSnapshot(content);\n\tconst innerWidth = Math.max(0, width - 4);\n\treturn [\n\t\t// One wordmark only: the framed title chip carries the product name, so the\n\t\t// hero body is the block mark plus live state, not a second text logotype.\n\t\tboxTop(width, `omk v${content.version} · OMK://CONTROL`),\n\t\tboxTextLine(width, sparkleRow(innerWidth, SPARKLE_ROW_TOP, sparkleMs)),\n\t\t...(bannerFrame ?? CONTROL_PANEL_ASCII_ART).map((line, index, rows) => {\n\t\t\tif (bannerFrame) return boxCenteredLine(width, line);\n\t\t\t// Single-tone wordmark: the top and bottom scanlines fall back to the\n\t\t\t// dim ink so the mark reads as one lit object rather than a four-colour\n\t\t\t// stack. Accent stays the only hue in the hero.\n\t\t\tconst isEdge = index === 0 || index === rows.length - 1;\n\t\t\tconst isCore = index === Math.floor(rows.length / 2);\n\t\t\tconst painted = theme.fg(isEdge ? \"dim\" : \"accent\", line);\n\t\t\treturn boxCenteredLine(width, isCore ? theme.bold(painted) : painted);\n\t\t}),\n\t\tboxTextLine(width, sparkleRow(innerWidth, SPARKLE_ROW_BOTTOM, sparkleMs)),\n\t\tboxCenteredLine(\n\t\t\twidth,\n\t\t\t`${theme.fg(\"success\", \"●\")} ${theme.fg(\"muted\", \"MODEL\")} ${theme.fg(\"text\", heroModelLabel(snapshot))}`,\n\t\t),\n\t\tboxCenteredLine(\n\t\t\twidth,\n\t\t\t[\"route\", \"verify\", \"loop\", \"control\"]\n\t\t\t\t.map((stage) => theme.fg(\"dim\", stage))\n\t\t\t\t.join(theme.fg(\"borderMuted\", \" · \")),\n\t\t),\n\t\tboxTextLine(width, \"\"),\n\t\tboxRuleLine(width, Math.max(4, Math.floor(innerWidth * 0.18))),\n\t\tboxCenteredLine(width, heroMetaStrip(snapshot)),\n\t\tboxBottom(width),\n\t];\n}\n\n/**\n * Compact meta strip under the wordmark: the same signals the narrow layout\n * shows beside the banner, rendered as one centred row so the hero carries\n * real state instead of empty deck height.\n */\nfunction heroMetaStrip(snapshot: ControlPanelStatusSnapshot): string {\n\tconst rawThemeName = theme.name ?? \"live\";\n\tconst themeName = (rawThemeName.startsWith(\"omk-\") ? rawThemeName.slice(4) : rawThemeName).toUpperCase();\n\tconst cells: Array<[string, string]> = [\n\t\t[\"PANEL\", snapshot.runtimeState ?? \"unknown\"],\n\t\t[\"THEME\", themeName],\n\t\t[\"STARTUP\", snapshot.startupState ?? \"unknown\"],\n\t\t[\"LINK\", snapshot.linkState ?? \"unknown\"],\n\t];\n\treturn cells\n\t\t.map(([label, value]) => `${theme.fg(\"muted\", label)} ${coloredStatus(value)}`)\n\t\t.join(theme.fg(\"borderMuted\", \" · \"));\n}\n\nfunction heroModelLabel(snapshot: ControlPanelStatusSnapshot): string {\n\tif (!snapshot.modelId) return \"no-model\";\n\tconst think = snapshot.thinkingLevel;\n\treturn think && think !== \"off\" ? `${snapshot.modelId}:${think}` : snapshot.modelId;\n}\n\n/**\n * Label gutters are aligned per section rather than across the whole rail: a\n * global gutter would indent short labels so far that long values (todo text,\n * model ids) lose characters at rail widths of 34-38 columns.\n */\nconst RAIL_COLUMNS = {\n\tstatus: 8,\n\ttodo: 4,\n\tsession: 3,\n\tmodel: 5,\n\truntime: 8,\n\tcontrol: 7,\n} as const;\n\nfunction sidebarPanel(content: ControlPanelContent, width: number): string[] {\n\tconst snapshot = statusSnapshot(content);\n\tconst headroomLabel = snapshot.headroomStatus ?? snapshot.optimizerPolicy ?? \"unknown\";\n\tconst mcpCount = snapshot.mcpCount;\n\tconst skillCount = snapshot.skillCount;\n\tconst packageIntakeLabel = packageIntakeStatusLabel(snapshot.packageIntake);\n\tconst contextMeter = contextMeterLabel(snapshot);\n\tconst activitySparkline = activitySparklineLabel(snapshot);\n\treturn [\n\t\tsidebarTabs(width),\n\t\tboxCenteredLine(width, theme.bold(theme.fg(\"accent\", \"OMK://CONTROL\"))),\n\t\tboxCenteredLine(width, theme.fg(\"dim\", \"CYBERPUNK OPS CORE\")),\n\t\tboxCenteredLine(\n\t\t\twidth,\n\t\t\t`${theme.fg(\"muted\", \"MATRIX RAIN\")}${theme.fg(\"borderMuted\", \" // \")}${theme.fg(\"muted\", \"NEON GRID ONLINE\")}`,\n\t\t),\n\t\tboxCenteredLine(width, theme.fg(\"dim\", \"NIGHT-CITY-MATRIX-V3\")),\n\t\tsidebarRule(width, \"STATUS\"),\n\t\tboxTextLine(\n\t\t\twidth,\n\t\t\t`${labelCell(\"state\", RAIL_COLUMNS.status)} ${coloredStatus(snapshot.runtimeState ?? \"unknown\")}`,\n\t\t),\n\t\tboxTextLine(\n\t\t\twidth,\n\t\t\t`${labelCell(\"route\", RAIL_COLUMNS.status)} ${coloredStatus(snapshot.routeState ?? \"unknown\")}`,\n\t\t),\n\t\tboxTextLine(\n\t\t\twidth,\n\t\t\t`${labelCell(\"evidence\", RAIL_COLUMNS.status)} ${coloredStatus(snapshot.evidenceState ?? \"unknown\")}`,\n\t\t),\n\t\tsidebarRule(width, \"TODO\"),\n\t\t...todoSidebarLines(snapshot.todoState, width),\n\t\tsidebarRule(width, \"SESSION\"),\n\t\tsemanticBoxTextLine(width, \"cwd\", snapshot.cwdLabel ?? \"?\", \"end\", RAIL_COLUMNS.session),\n\t\tsemanticBoxTextLine(width, \"git\", snapshot.gitBranch ?? \"?\", \"start\", RAIL_COLUMNS.session),\n\t\tsidebarRule(width, \"MODEL / CTX\"),\n\t\tsemanticBoxTextLine(width, \"model\", modelStatusLabel(snapshot), \"end\", RAIL_COLUMNS.model),\n\t\tsemanticBoxTextLine(width, \"think\", snapshot.thinkingLevel ?? \"off\", \"start\", RAIL_COLUMNS.model),\n\t\tboxTextLine(\n\t\t\twidth,\n\t\t\t`${labelCell(\"ctx\", RAIL_COLUMNS.model)} ${theme.fg(statusColor(snapshot.contextPercent === undefined || snapshot.contextPercent === null ? \"unknown\" : \"ready\"), contextStatusLabel(snapshot))}`,\n\t\t),\n\t\tboxTextLine(width, `${labelCell(\"meter\", RAIL_COLUMNS.model)} ${contextMeter}`),\n\t\tboxTextLine(width, `${labelCell(\"pulse\", RAIL_COLUMNS.model)} ${activitySparkline}`),\n\t\tsidebarRule(width, \"RUNTIME / MCP / SKILLS\"),\n\t\tsemanticBoxTextLine(width, \"headroom\", headroomLabel, \"end\", RAIL_COLUMNS.runtime),\n\t\tsemanticBoxTextLine(width, \"omk\", snapshot.dagOrchestrationState ?? \"unknown\", \"end\", RAIL_COLUMNS.runtime),\n\t\tsemanticBoxTextLine(width, \"sidebar\", snapshot.sidebarState ?? \"unknown\", \"start\", RAIL_COLUMNS.runtime),\n\t\tboxTextLine(\n\t\t\twidth,\n\t\t\t`${labelCell(\"res\", RAIL_COLUMNS.runtime)} MCP:${mcpCount ?? \"?\"} skills:${skillCount ?? \"?\"}`,\n\t\t),\n\t\tsemanticBoxTextLine(width, \"pkg\", packageIntakeLabel, \"end\", RAIL_COLUMNS.runtime),\n\t\t// CONTROL previously repeated STATUS's route row verbatim; the rail now\n\t\t// carries each signal exactly once.\n\t\tsidebarRule(width, \"CONTROL\"),\n\t\tboxTextLine(\n\t\t\twidth,\n\t\t\t`${labelCell(\"verify\", RAIL_COLUMNS.control)} ${coloredStatus(snapshot.evidenceState ?? \"unknown\")}`,\n\t\t),\n\t\tboxTextLine(\n\t\t\twidth,\n\t\t\t`${labelCell(\"control\", RAIL_COLUMNS.control)} ${coloredStatus(snapshot.controlState ?? \"unknown\")}`,\n\t\t),\n\t\tboxBottom(width),\n\t];\n}\n\nfunction semanticBoxTextLine(\n\twidth: number,\n\tlabel: string,\n\tvalue: string,\n\tpreserve: \"start\" | \"middle\" | \"end\",\n\tcolumn?: number,\n): string {\n\t// Alignment is a courtesy, not a cost: if the gutter indent would truncate the\n\t// value, this row drops back to a flush label so the data survives intact.\n\tconst aligned = `${labelCell(label, column)} `;\n\tconst flush = `${labelCell(label, label.length)} `;\n\tconst alignedRoom = Math.max(0, width - 4 - visibleWidth(aligned));\n\tconst prefix = visibleWidth(value) <= alignedRoom ? aligned : flush;\n\tconst availableWidth = Math.max(0, width - 4 - visibleWidth(prefix));\n\treturn boxTextLine(width, `${prefix}${semanticTruncate(value, availableWidth, preserve)}`);\n}\n\nfunction semanticTruncate(value: string, maxWidth: number, preserve: \"start\" | \"middle\" | \"end\"): string {\n\tif (visibleWidth(value) <= maxWidth) return value;\n\tif (maxWidth <= 0) return \"\";\n\tif (preserve === \"start\") return truncateToWidth(value, maxWidth, \"…\");\n\tconst ellipsis = \"…\";\n\tconst targetWidth = maxWidth - visibleWidth(ellipsis);\n\tif (targetWidth <= 0) return truncateToWidth(ellipsis, maxWidth, \"\");\n\tif (preserve === \"middle\") {\n\t\tconst headWidth = Math.max(1, Math.floor(targetWidth / 2));\n\t\tconst tailWidth = Math.max(1, targetWidth - headWidth);\n\t\treturn `${takeStart(value, headWidth)}${ellipsis}${takeEnd(value, tailWidth)}`;\n\t}\n\treturn `${ellipsis}${takeEnd(value, targetWidth)}`;\n}\n\nfunction takeStart(value: string, maxWidth: number): string {\n\tlet prefix = \"\";\n\tfor (const char of Array.from(value)) {\n\t\tif (visibleWidth(prefix + char) > maxWidth) break;\n\t\tprefix += char;\n\t}\n\treturn prefix;\n}\n\nfunction takeEnd(value: string, maxWidth: number): string {\n\tlet suffix = \"\";\n\tfor (const char of Array.from(value).reverse()) {\n\t\tif (visibleWidth(char + suffix) > maxWidth) break;\n\t\tsuffix = char + suffix;\n\t}\n\treturn suffix;\n}\n\nfunction statusToken(label: string, value: string | undefined): string {\n\tconst state = value ?? \"unknown\";\n\treturn theme.fg(statusColor(state), `${label}:${state.toUpperCase()}`);\n}\n\nfunction coloredStatus(value: string): string {\n\treturn theme.fg(statusColor(value), value);\n}\n\nfunction statusColor(value: string): ThemeColor {\n\tconst normalized = value.toLowerCase();\n\tif ([\"ready\", \"on\", \"active\", \"available\", \"linked\", \"tracking\", \"pinned\"].includes(normalized)) return \"success\";\n\tif ([\"degraded\", \"limited\", \"blocked\", \"off\"].includes(normalized)) return \"warning\";\n\treturn \"muted\";\n}\n\nfunction todoSidebarLines(state: TodoState | undefined, width: number): string[] {\n\tif (!state || state.items.length === 0) {\n\t\treturn [\n\t\t\tboxTextLine(width, `${labelCell(\"todo\", RAIL_COLUMNS.todo)} ${theme.fg(\"dim\", \"empty\")}`),\n\t\t\tboxTextLine(width, `${labelCell(\"next\", RAIL_COLUMNS.todo)} ${theme.fg(\"dim\", \"no active todos\")}`),\n\t\t];\n\t}\n\tconst counts = todoSummary(state);\n\tconst next = nextActiveTodo(state);\n\treturn [\n\t\tboxTextLine(width, `${labelCell(\"todo\", RAIL_COLUMNS.todo)} ${counts.done}/${counts.total} done`),\n\t\tsemanticBoxTextLine(width, \"next\", next?.label ?? \"complete\", \"middle\", RAIL_COLUMNS.todo),\n\t];\n}\n\nfunction packageIntakeStatusLabel(summary: PiPackageIntakeSummary | undefined): string {\n\tif (!summary) return \"ports:pending\";\n\tconst ready =\n\t\tsummary.acceptedNative + summary.acceptedReference + summary.acceptedMeasurement + summary.acceptedAdvisory;\n\tconst review = summary.deferred + summary.reject;\n\treturn `ports:${ready}/${summary.total} review:${review} block:${summary.hardForkBlocked}`;\n}\n\nfunction renderExpanded(content: ControlPanelContent, width: number, bannerFrame?: string[], sparkleMs = 0): string[] {\n\tif (width >= CONTROL_PANEL_DECK_MIN_WIDTH) {\n\t\tconst { leftWidth, sidebarWidth } = deckWidths(width);\n\t\tconst resourceLines = [\"\", ...content.onboarding().split(\"\\n\")];\n\t\tconst rightRail = blankSidebarRail(sidebarWidth, resourceLines.length);\n\t\tconst lines = [...renderDeck(content, width, bannerFrame, sparkleMs)];\n\t\tlines.push(...composeColumns(resourceLines, leftWidth, rightRail, sidebarWidth, CONTROL_PANEL_GAP_WIDTH, width));\n\t\treturn lines;\n\t}\n\n\tconst lines = [divider(width, \"OMK//CONTROL PANEL\", \"accent\"), statusLine(content, width)];\n\tif (width >= 32) lines.push(...brandLines(content, width, bannerFrame));\n\tlines.push(divider(width, \"SYSTEM MAP\", \"mdHeading\"));\n\tfor (const instruction of content.expandedInstructions().split(\"\\n\")) lines.push(textLine(width, instruction));\n\tlines.push(divider(width, \"STARTUP LINK\", \"success\"));\n\tfor (const onboardingLine of content.onboarding().split(\"\\n\")) lines.push(textLine(width, onboardingLine, \"dim\"));\n\tlines.push(divider(width, \"END\", \"borderMuted\"));\n\treturn lines;\n}\n\nfunction blankSidebarRail(width: number, lineCount: number): string[] {\n\treturn Array.from({ length: lineCount }, () => boxTextLine(width, \"\"));\n}\n\nfunction statusSnapshot(content: ControlPanelContent): ControlPanelStatusSnapshot {\n\treturn content.statusSnapshot?.() ?? {};\n}\n\nfunction modelStatusLabel(snapshot: ControlPanelStatusSnapshot): string {\n\tif (!snapshot.modelId) return \"no-model\";\n\treturn snapshot.modelProvider ? `${snapshot.modelProvider}/${snapshot.modelId}` : snapshot.modelId;\n}\n\nfunction contextStatusLabel(snapshot: ControlPanelStatusSnapshot): string {\n\tconst windowTokens = snapshot.contextWindowTokens ?? 0;\n\tconst windowLabel = windowTokens > 0 ? formatTokens(windowTokens) : \"?\";\n\treturn snapshot.contextPercent === null || snapshot.contextPercent === undefined\n\t\t? `?/${windowLabel}`\n\t\t: `${snapshot.contextPercent.toFixed(1)}%/${windowLabel}`;\n}\n\nfunction contextMeterLabel(snapshot: ControlPanelStatusSnapshot): string {\n\tconst percent = normalizePercent(snapshot.contextPercent);\n\tif (percent === undefined) {\n\t\treturn `${theme.fg(\"borderMuted\", \"░\".repeat(CONTEXT_METER_CELLS))} ${theme.fg(\"muted\", \"??%\")}`;\n\t}\n\tconst filled = Math.max(0, Math.min(CONTEXT_METER_CELLS, Math.round((percent / 100) * CONTEXT_METER_CELLS)));\n\tconst color: ThemeColor = percent >= 85 ? \"warning\" : percent >= 65 ? \"mdCode\" : \"success\";\n\treturn `${theme.fg(color, \"█\".repeat(filled))}${theme.fg(\"borderMuted\", \"░\".repeat(CONTEXT_METER_CELLS - filled))} ${theme.fg(color, `${Math.round(percent)}%`)}`;\n}\n\nfunction activitySparklineLabel(snapshot: ControlPanelStatusSnapshot): string {\n\tconst seed = hashSnapshot(snapshot);\n\tconst glyphs = Array.from(SPARKLINE_GLYPHS);\n\tconst parts: string[] = [];\n\tfor (let index = 0; index < SPARKLINE_WIDTH; index++) {\n\t\tconst level = (seed + index * 5 + index * index) % glyphs.length;\n\t\tconst glyph = glyphs[level] ?? \"▁\";\n\t\t// Colour follows amplitude instead of column index, so the pulse reads as\n\t\t// one signal in the accent hue rather than a rotating three-colour pattern.\n\t\tconst color: ThemeColor = level >= glyphs.length - 2 ? \"accent\" : level >= 3 ? \"muted\" : \"dim\";\n\t\tparts.push(theme.fg(color, glyph));\n\t}\n\treturn parts.join(\"\");\n}\n\nfunction normalizePercent(value: number | null | undefined): number | undefined {\n\tif (value === null || value === undefined || !Number.isFinite(value)) return undefined;\n\tif (value < 0) return 0;\n\tif (value > 100) return 100;\n\treturn value;\n}\n\nfunction hashSnapshot(snapshot: ControlPanelStatusSnapshot): number {\n\tconst source = [\n\t\tsnapshot.modelProvider,\n\t\tsnapshot.modelId,\n\t\tsnapshot.contextPercent?.toFixed(1),\n\t\tsnapshot.contextTokens,\n\t\tsnapshot.contextWindowTokens,\n\t\tsnapshot.cwdLabel,\n\t\tsnapshot.gitBranch,\n\t].join(\"|\");\n\tlet hash = 2166136261;\n\tfor (let index = 0; index < source.length; index++) {\n\t\thash ^= source.charCodeAt(index);\n\t\thash = Math.imul(hash, 16777619);\n\t}\n\treturn hash >>> 0;\n}\n\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\nfunction sidebarTabs(width: number): string {\n\treturn boxTextLine(\n\t\twidth,\n\t\tfitLine(\n\t\t\t`${theme.bold(theme.fg(\"accent\", \"1:CONTROL\"))} ${theme.fg(\"muted\", \"2:HISTORY\")}`,\n\t\t\tMath.max(0, width - 4),\n\t\t),\n\t);\n}\n\nfunction controlStripLine(content: ControlPanelContent, width: number): string {\n\treturn centerLine(\n\t\twidth,\n\t\t`${theme.bold(theme.fg(\"accent\", \"OMK://CONTROL READ\"))} ${theme.fg(\"accent\", \"route/verify/loop/control\")} · ${content.compactInstructions()}`,\n\t);\n}\n\nfunction brandLines(content: ControlPanelContent, width: number, bannerFrame?: string[]): string[] {\n\tconst art = bannerFrame ?? CONTROL_PANEL_ASCII_ART;\n\tif (bannerFrame) return art.map((line) => textLine(width, line));\n\tconst leftWidth = Math.max(...art.map((line) => visibleWidth(line)));\n\tconst minWideWidth = visibleWidth(\"| \") + leftWidth + visibleWidth(\" | \") + META_WIDTH;\n\tif (width < minWideWidth) return art.map((line) => textLine(width, theme.fg(\"accent\", line)));\n\tconst snapshot = statusSnapshot(content);\n\treturn art.map((line, index) =>\n\t\ttextLine(\n\t\t\twidth,\n\t\t\t`${fitLine(theme.fg(\"accent\", line), leftWidth)}${theme.fg(\"borderMuted\", \" | \")}${metadataLines(snapshot)[index] ?? \"\"}`,\n\t\t),\n\t);\n}\n\nfunction metadataLines(snapshot: ControlPanelStatusSnapshot): string[] {\n\tconst rawThemeName = theme.name ?? \"live\";\n\tconst themeName = (rawThemeName.startsWith(\"omk-\") ? rawThemeName.slice(4) : rawThemeName).toUpperCase();\n\treturn [\n\t\t`${theme.fg(\"mdCode\", \"PANEL\")} ${coloredStatus(snapshot.runtimeState ?? \"unknown\")}`,\n\t\t`${theme.fg(\"mdCode\", \"THEME\")} ${theme.fg(\"accent\", themeName)}`,\n\t\t`${theme.fg(\"mdCode\", \"STARTUP\")} ${coloredStatus(snapshot.startupState ?? \"unknown\")}`,\n\t\t`${theme.fg(\"mdCode\", \"LINK\")} ${coloredStatus(snapshot.linkState ?? \"unknown\")}`,\n\t];\n}\n\nfunction statusLine(content: ControlPanelContent, width: number): string {\n\tconst snapshot = statusSnapshot(content);\n\tconst segments = [\n\t\ttheme.bold(theme.fg(\"accent\", `${content.appName.toUpperCase()} v${content.version}`)),\n\t\tstatusToken(\"CORE\", snapshot.runtimeState),\n\t\tstatusToken(\"ANSI\", snapshot.ansiColorState),\n\t\tstatusToken(\"STARTUP\", snapshot.startupState),\n\t\tstatusToken(\"LINK\", snapshot.linkState),\n\t\ttheme.fg(\"muted\", \"THEME:LIVE\"),\n\t];\n\treturn textLine(width, segments.join(theme.fg(\"borderMuted\", \" | \")));\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { truncateToWidth, visibleWidth } from "omk-tui";
|
|
2
2
|
import { nextActiveTodo, summary as todoSummary } from "../../../core/todo-state.js";
|
|
3
3
|
import { theme } from "../theme/theme.js";
|
|
4
|
-
import { boxBottom, boxCenteredLine, boxTextLine, boxTop, centerLine, composeColumns, divider, fitLine, sidebarRule, textLine, } from "./control-panel-box.js";
|
|
4
|
+
import { boxBottom, boxCenteredLine, boxRuleLine, boxTextLine, boxTop, centerLine, composeColumns, divider, fitLine, labelCell, padBoxColumn, sidebarRule, textLine, } from "./control-panel-box.js";
|
|
5
5
|
import { SPARKLE_ROW_BOTTOM, SPARKLE_ROW_TOP, sparkleRow } from "./control-panel-sparkles.js";
|
|
6
6
|
export const CONTROL_PANEL_ASCII_ART = [
|
|
7
7
|
" ███████ ██ ██ ██ ██",
|
|
@@ -49,7 +49,12 @@ function renderDeck(content, width, bannerFrame, sparkleMs = 0) {
|
|
|
49
49
|
const { leftWidth, sidebarWidth } = deckWidths(width);
|
|
50
50
|
if (leftWidth < 72)
|
|
51
51
|
return [];
|
|
52
|
-
const
|
|
52
|
+
const hero = heroPanel(content, leftWidth, bannerFrame, sparkleMs);
|
|
53
|
+
const rail = sidebarPanel(content, sidebarWidth);
|
|
54
|
+
// Both framed columns close on the same row so the deck reads as one block
|
|
55
|
+
// instead of a short hero beside a long ragged rail.
|
|
56
|
+
const deckHeight = Math.max(hero.length, rail.length);
|
|
57
|
+
const lines = composeColumns(padBoxColumn(hero, deckHeight, leftWidth, "center"), leftWidth, padBoxColumn(rail, deckHeight, sidebarWidth), sidebarWidth, CONTROL_PANEL_GAP_WIDTH, width);
|
|
53
58
|
lines.push(controlStripLine(content, width));
|
|
54
59
|
lines.push(centerLine(width, content.compactOnboarding(), "dim"));
|
|
55
60
|
return lines;
|
|
@@ -62,28 +67,69 @@ function heroPanel(content, width, bannerFrame, sparkleMs = 0) {
|
|
|
62
67
|
const snapshot = statusSnapshot(content);
|
|
63
68
|
const innerWidth = Math.max(0, width - 4);
|
|
64
69
|
return [
|
|
70
|
+
// One wordmark only: the framed title chip carries the product name, so the
|
|
71
|
+
// hero body is the block mark plus live state, not a second text logotype.
|
|
65
72
|
boxTop(width, `omk v${content.version} · OMK://CONTROL`),
|
|
66
|
-
boxCenteredLine(width, theme.bold(theme.fg("accent", "OMK"))),
|
|
67
|
-
boxCenteredLine(width, theme.fg("muted", "route · verify · loop · control")),
|
|
68
73
|
boxTextLine(width, sparkleRow(innerWidth, SPARKLE_ROW_TOP, sparkleMs)),
|
|
69
|
-
...(bannerFrame ?? CONTROL_PANEL_ASCII_ART).map((line, index) => {
|
|
74
|
+
...(bannerFrame ?? CONTROL_PANEL_ASCII_ART).map((line, index, rows) => {
|
|
70
75
|
if (bannerFrame)
|
|
71
76
|
return boxCenteredLine(width, line);
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
// Single-tone wordmark: the top and bottom scanlines fall back to the
|
|
78
|
+
// dim ink so the mark reads as one lit object rather than a four-colour
|
|
79
|
+
// stack. Accent stays the only hue in the hero.
|
|
80
|
+
const isEdge = index === 0 || index === rows.length - 1;
|
|
81
|
+
const isCore = index === Math.floor(rows.length / 2);
|
|
82
|
+
const painted = theme.fg(isEdge ? "dim" : "accent", line);
|
|
83
|
+
return boxCenteredLine(width, isCore ? theme.bold(painted) : painted);
|
|
74
84
|
}),
|
|
75
85
|
boxTextLine(width, sparkleRow(innerWidth, SPARKLE_ROW_BOTTOM, sparkleMs)),
|
|
76
|
-
boxCenteredLine(width, `${theme.fg("
|
|
77
|
-
boxCenteredLine(width,
|
|
86
|
+
boxCenteredLine(width, `${theme.fg("success", "●")} ${theme.fg("muted", "MODEL")} ${theme.fg("text", heroModelLabel(snapshot))}`),
|
|
87
|
+
boxCenteredLine(width, ["route", "verify", "loop", "control"]
|
|
88
|
+
.map((stage) => theme.fg("dim", stage))
|
|
89
|
+
.join(theme.fg("borderMuted", " · "))),
|
|
90
|
+
boxTextLine(width, ""),
|
|
91
|
+
boxRuleLine(width, Math.max(4, Math.floor(innerWidth * 0.18))),
|
|
92
|
+
boxCenteredLine(width, heroMetaStrip(snapshot)),
|
|
78
93
|
boxBottom(width),
|
|
79
94
|
];
|
|
80
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Compact meta strip under the wordmark: the same signals the narrow layout
|
|
98
|
+
* shows beside the banner, rendered as one centred row so the hero carries
|
|
99
|
+
* real state instead of empty deck height.
|
|
100
|
+
*/
|
|
101
|
+
function heroMetaStrip(snapshot) {
|
|
102
|
+
const rawThemeName = theme.name ?? "live";
|
|
103
|
+
const themeName = (rawThemeName.startsWith("omk-") ? rawThemeName.slice(4) : rawThemeName).toUpperCase();
|
|
104
|
+
const cells = [
|
|
105
|
+
["PANEL", snapshot.runtimeState ?? "unknown"],
|
|
106
|
+
["THEME", themeName],
|
|
107
|
+
["STARTUP", snapshot.startupState ?? "unknown"],
|
|
108
|
+
["LINK", snapshot.linkState ?? "unknown"],
|
|
109
|
+
];
|
|
110
|
+
return cells
|
|
111
|
+
.map(([label, value]) => `${theme.fg("muted", label)} ${coloredStatus(value)}`)
|
|
112
|
+
.join(theme.fg("borderMuted", " · "));
|
|
113
|
+
}
|
|
81
114
|
function heroModelLabel(snapshot) {
|
|
82
115
|
if (!snapshot.modelId)
|
|
83
116
|
return "no-model";
|
|
84
117
|
const think = snapshot.thinkingLevel;
|
|
85
118
|
return think && think !== "off" ? `${snapshot.modelId}:${think}` : snapshot.modelId;
|
|
86
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Label gutters are aligned per section rather than across the whole rail: a
|
|
122
|
+
* global gutter would indent short labels so far that long values (todo text,
|
|
123
|
+
* model ids) lose characters at rail widths of 34-38 columns.
|
|
124
|
+
*/
|
|
125
|
+
const RAIL_COLUMNS = {
|
|
126
|
+
status: 8,
|
|
127
|
+
todo: 4,
|
|
128
|
+
session: 3,
|
|
129
|
+
model: 5,
|
|
130
|
+
runtime: 8,
|
|
131
|
+
control: 7,
|
|
132
|
+
};
|
|
87
133
|
function sidebarPanel(content, width) {
|
|
88
134
|
const snapshot = statusSnapshot(content);
|
|
89
135
|
const headroomLabel = snapshot.headroomStatus ?? snapshot.optimizerPolicy ?? "unknown";
|
|
@@ -95,39 +141,45 @@ function sidebarPanel(content, width) {
|
|
|
95
141
|
return [
|
|
96
142
|
sidebarTabs(width),
|
|
97
143
|
boxCenteredLine(width, theme.bold(theme.fg("accent", "OMK://CONTROL"))),
|
|
98
|
-
boxCenteredLine(width, theme.
|
|
99
|
-
boxCenteredLine(width, `${theme.fg("
|
|
100
|
-
boxCenteredLine(width, theme.fg("
|
|
101
|
-
sidebarRule(width, "STATUS"
|
|
102
|
-
boxTextLine(width, `${
|
|
103
|
-
boxTextLine(width, `${
|
|
104
|
-
boxTextLine(width, `${
|
|
105
|
-
sidebarRule(width, "TODO"
|
|
144
|
+
boxCenteredLine(width, theme.fg("dim", "CYBERPUNK OPS CORE")),
|
|
145
|
+
boxCenteredLine(width, `${theme.fg("muted", "MATRIX RAIN")}${theme.fg("borderMuted", " // ")}${theme.fg("muted", "NEON GRID ONLINE")}`),
|
|
146
|
+
boxCenteredLine(width, theme.fg("dim", "NIGHT-CITY-MATRIX-V3")),
|
|
147
|
+
sidebarRule(width, "STATUS"),
|
|
148
|
+
boxTextLine(width, `${labelCell("state", RAIL_COLUMNS.status)} ${coloredStatus(snapshot.runtimeState ?? "unknown")}`),
|
|
149
|
+
boxTextLine(width, `${labelCell("route", RAIL_COLUMNS.status)} ${coloredStatus(snapshot.routeState ?? "unknown")}`),
|
|
150
|
+
boxTextLine(width, `${labelCell("evidence", RAIL_COLUMNS.status)} ${coloredStatus(snapshot.evidenceState ?? "unknown")}`),
|
|
151
|
+
sidebarRule(width, "TODO"),
|
|
106
152
|
...todoSidebarLines(snapshot.todoState, width),
|
|
107
|
-
sidebarRule(width, "SESSION"
|
|
108
|
-
semanticBoxTextLine(width, "cwd", snapshot.cwdLabel ?? "?", "end"),
|
|
109
|
-
semanticBoxTextLine(width, "git", snapshot.gitBranch ?? "?", "start"),
|
|
110
|
-
sidebarRule(width, "MODEL / CTX"
|
|
111
|
-
semanticBoxTextLine(width, "model", modelStatusLabel(snapshot), "end"),
|
|
112
|
-
semanticBoxTextLine(width, "think", snapshot.thinkingLevel ?? "off", "start"),
|
|
113
|
-
boxTextLine(width, `${
|
|
114
|
-
boxTextLine(width, `${
|
|
115
|
-
boxTextLine(width, `${
|
|
116
|
-
sidebarRule(width, "RUNTIME / MCP / SKILLS"
|
|
117
|
-
semanticBoxTextLine(width, "headroom", headroomLabel, "end"),
|
|
118
|
-
semanticBoxTextLine(width, "omk", snapshot.dagOrchestrationState ?? "unknown", "end"),
|
|
119
|
-
semanticBoxTextLine(width, "sidebar", snapshot.sidebarState ?? "unknown", "start"),
|
|
120
|
-
boxTextLine(width, `${
|
|
121
|
-
semanticBoxTextLine(width, "pkg", packageIntakeLabel, "end"),
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
boxTextLine(width, `${
|
|
126
|
-
|
|
153
|
+
sidebarRule(width, "SESSION"),
|
|
154
|
+
semanticBoxTextLine(width, "cwd", snapshot.cwdLabel ?? "?", "end", RAIL_COLUMNS.session),
|
|
155
|
+
semanticBoxTextLine(width, "git", snapshot.gitBranch ?? "?", "start", RAIL_COLUMNS.session),
|
|
156
|
+
sidebarRule(width, "MODEL / CTX"),
|
|
157
|
+
semanticBoxTextLine(width, "model", modelStatusLabel(snapshot), "end", RAIL_COLUMNS.model),
|
|
158
|
+
semanticBoxTextLine(width, "think", snapshot.thinkingLevel ?? "off", "start", RAIL_COLUMNS.model),
|
|
159
|
+
boxTextLine(width, `${labelCell("ctx", RAIL_COLUMNS.model)} ${theme.fg(statusColor(snapshot.contextPercent === undefined || snapshot.contextPercent === null ? "unknown" : "ready"), contextStatusLabel(snapshot))}`),
|
|
160
|
+
boxTextLine(width, `${labelCell("meter", RAIL_COLUMNS.model)} ${contextMeter}`),
|
|
161
|
+
boxTextLine(width, `${labelCell("pulse", RAIL_COLUMNS.model)} ${activitySparkline}`),
|
|
162
|
+
sidebarRule(width, "RUNTIME / MCP / SKILLS"),
|
|
163
|
+
semanticBoxTextLine(width, "headroom", headroomLabel, "end", RAIL_COLUMNS.runtime),
|
|
164
|
+
semanticBoxTextLine(width, "omk", snapshot.dagOrchestrationState ?? "unknown", "end", RAIL_COLUMNS.runtime),
|
|
165
|
+
semanticBoxTextLine(width, "sidebar", snapshot.sidebarState ?? "unknown", "start", RAIL_COLUMNS.runtime),
|
|
166
|
+
boxTextLine(width, `${labelCell("res", RAIL_COLUMNS.runtime)} MCP:${mcpCount ?? "?"} skills:${skillCount ?? "?"}`),
|
|
167
|
+
semanticBoxTextLine(width, "pkg", packageIntakeLabel, "end", RAIL_COLUMNS.runtime),
|
|
168
|
+
// CONTROL previously repeated STATUS's route row verbatim; the rail now
|
|
169
|
+
// carries each signal exactly once.
|
|
170
|
+
sidebarRule(width, "CONTROL"),
|
|
171
|
+
boxTextLine(width, `${labelCell("verify", RAIL_COLUMNS.control)} ${coloredStatus(snapshot.evidenceState ?? "unknown")}`),
|
|
172
|
+
boxTextLine(width, `${labelCell("control", RAIL_COLUMNS.control)} ${coloredStatus(snapshot.controlState ?? "unknown")}`),
|
|
173
|
+
boxBottom(width),
|
|
127
174
|
];
|
|
128
175
|
}
|
|
129
|
-
function semanticBoxTextLine(width, label, value, preserve) {
|
|
130
|
-
|
|
176
|
+
function semanticBoxTextLine(width, label, value, preserve, column) {
|
|
177
|
+
// Alignment is a courtesy, not a cost: if the gutter indent would truncate the
|
|
178
|
+
// value, this row drops back to a flush label so the data survives intact.
|
|
179
|
+
const aligned = `${labelCell(label, column)} `;
|
|
180
|
+
const flush = `${labelCell(label, label.length)} `;
|
|
181
|
+
const alignedRoom = Math.max(0, width - 4 - visibleWidth(aligned));
|
|
182
|
+
const prefix = visibleWidth(value) <= alignedRoom ? aligned : flush;
|
|
131
183
|
const availableWidth = Math.max(0, width - 4 - visibleWidth(prefix));
|
|
132
184
|
return boxTextLine(width, `${prefix}${semanticTruncate(value, availableWidth, preserve)}`);
|
|
133
185
|
}
|
|
@@ -185,15 +237,15 @@ function statusColor(value) {
|
|
|
185
237
|
function todoSidebarLines(state, width) {
|
|
186
238
|
if (!state || state.items.length === 0) {
|
|
187
239
|
return [
|
|
188
|
-
boxTextLine(width, `${theme.fg("
|
|
189
|
-
boxTextLine(width, `${theme.fg("
|
|
240
|
+
boxTextLine(width, `${labelCell("todo", RAIL_COLUMNS.todo)} ${theme.fg("dim", "empty")}`),
|
|
241
|
+
boxTextLine(width, `${labelCell("next", RAIL_COLUMNS.todo)} ${theme.fg("dim", "no active todos")}`),
|
|
190
242
|
];
|
|
191
243
|
}
|
|
192
244
|
const counts = todoSummary(state);
|
|
193
245
|
const next = nextActiveTodo(state);
|
|
194
246
|
return [
|
|
195
|
-
boxTextLine(width, `${
|
|
196
|
-
semanticBoxTextLine(width, "next", next?.label ?? "complete", "middle"),
|
|
247
|
+
boxTextLine(width, `${labelCell("todo", RAIL_COLUMNS.todo)} ${counts.done}/${counts.total} done`),
|
|
248
|
+
semanticBoxTextLine(width, "next", next?.label ?? "complete", "middle", RAIL_COLUMNS.todo),
|
|
197
249
|
];
|
|
198
250
|
}
|
|
199
251
|
function packageIntakeStatusLabel(summary) {
|
|
@@ -256,8 +308,11 @@ function activitySparklineLabel(snapshot) {
|
|
|
256
308
|
const glyphs = Array.from(SPARKLINE_GLYPHS);
|
|
257
309
|
const parts = [];
|
|
258
310
|
for (let index = 0; index < SPARKLINE_WIDTH; index++) {
|
|
259
|
-
const
|
|
260
|
-
const
|
|
311
|
+
const level = (seed + index * 5 + index * index) % glyphs.length;
|
|
312
|
+
const glyph = glyphs[level] ?? "▁";
|
|
313
|
+
// Colour follows amplitude instead of column index, so the pulse reads as
|
|
314
|
+
// one signal in the accent hue rather than a rotating three-colour pattern.
|
|
315
|
+
const color = level >= glyphs.length - 2 ? "accent" : level >= 3 ? "muted" : "dim";
|
|
261
316
|
parts.push(theme.fg(color, glyph));
|
|
262
317
|
}
|
|
263
318
|
return parts.join("");
|