@agimon-ai/doompi-ui 0.0.1-alpha.2 → 0.0.1-alpha.21
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/README.md +21 -18
- package/dist/_virtual/_rolldown/runtime.cjs +1 -1
- package/dist/adapters/pi/extension.cjs +2 -0
- package/dist/adapters/pi/extension.cjs.map +1 -0
- package/dist/adapters/pi/extension.d.cts +7 -0
- package/dist/adapters/pi/extension.d.cts.map +1 -0
- package/dist/adapters/pi/extension.d.mts +7 -0
- package/dist/adapters/pi/extension.d.mts.map +1 -0
- package/dist/adapters/pi/extension.mjs +2 -0
- package/dist/adapters/pi/extension.mjs.map +1 -0
- package/dist/adapters/pi/extensionName.cjs +2 -0
- package/dist/adapters/pi/extensionName.cjs.map +1 -0
- package/dist/adapters/pi/extensionName.d.cts +16 -0
- package/dist/adapters/pi/extensionName.d.cts.map +1 -0
- package/dist/adapters/pi/extensionName.d.mts +16 -0
- package/dist/adapters/pi/extensionName.d.mts.map +1 -0
- package/dist/adapters/pi/extensionName.mjs +2 -0
- package/dist/adapters/pi/extensionName.mjs.map +1 -0
- package/dist/adapters/pi/toolOverrides.cjs +2 -0
- package/dist/adapters/pi/toolOverrides.cjs.map +1 -0
- package/dist/adapters/pi/toolOverrides.mjs +2 -0
- package/dist/adapters/pi/toolOverrides.mjs.map +1 -0
- package/dist/adapters/telemetry/logSinkTelemetry.cjs +2 -0
- package/dist/adapters/telemetry/logSinkTelemetry.cjs.map +1 -0
- package/dist/adapters/telemetry/logSinkTelemetry.d.cts +29 -0
- package/dist/adapters/telemetry/logSinkTelemetry.d.cts.map +1 -0
- package/dist/adapters/telemetry/logSinkTelemetry.d.mts +29 -0
- package/dist/adapters/telemetry/logSinkTelemetry.d.mts.map +1 -0
- package/dist/adapters/telemetry/logSinkTelemetry.mjs +2 -0
- package/dist/adapters/telemetry/logSinkTelemetry.mjs.map +1 -0
- package/dist/components/configOverlay.cjs +0 -0
- package/dist/components/configOverlay.d.cts +0 -0
- package/dist/components/configOverlay.d.mts +0 -0
- package/dist/components/configOverlay.mjs +0 -0
- package/dist/components/doomEditor.cjs +1 -2
- package/dist/components/doomEditor.d.cts +2 -59
- package/dist/components/doomEditor.d.mts +2 -59
- package/dist/components/doomEditor.mjs +1 -2
- package/dist/components/doomFooter.cjs +1 -2
- package/dist/components/doomFooter.d.cts +2 -32
- package/dist/components/doomFooter.d.mts +2 -32
- package/dist/components/doomFooter.mjs +1 -2
- package/dist/components/doomHeader.cjs +1 -2
- package/dist/components/doomHeader.d.cts +2 -34
- package/dist/components/doomHeader.d.mts +2 -34
- package/dist/components/doomHeader.mjs +1 -2
- package/dist/components/doomOverlay.cjs +1 -2
- package/dist/components/doomOverlay.d.cts +2 -79
- package/dist/components/doomOverlay.d.mts +2 -79
- package/dist/components/doomOverlay.mjs +1 -2
- package/dist/components/leaderHints.cjs +1 -2
- package/dist/components/leaderHints.d.cts +2 -37
- package/dist/components/leaderHints.d.mts +2 -37
- package/dist/components/leaderHints.mjs +1 -2
- package/dist/components/toolsOverlay.cjs +1 -2
- package/dist/components/toolsOverlay.d.cts +2 -29
- package/dist/components/toolsOverlay.d.mts +2 -29
- package/dist/components/toolsOverlay.mjs +1 -2
- package/dist/config.cjs +1 -2
- package/dist/config.d.cts +2 -5
- package/dist/config.d.mts +2 -5
- package/dist/config.mjs +1 -2
- package/dist/extension.cjs +1 -2
- package/dist/extension.d.cts +2 -7
- package/dist/extension.d.mts +2 -7
- package/dist/extension.mjs +1 -2
- package/dist/extensionName.cjs +1 -2
- package/dist/extensionName.d.cts +2 -5
- package/dist/extensionName.d.mts +2 -5
- package/dist/extensionName.mjs +1 -2
- package/dist/extensions/pi.cjs +1 -1
- package/dist/extensions/pi.cjs.map +1 -1
- package/dist/extensions/pi.d.cts +1 -1
- package/dist/extensions/pi.d.mts +1 -1
- package/dist/extensions/pi.mjs +1 -1
- package/dist/extensions/pi.mjs.map +1 -1
- package/dist/footer.cjs +1 -2
- package/dist/footer.d.cts +2 -5
- package/dist/footer.d.mts +2 -5
- package/dist/footer.mjs +1 -2
- package/dist/harnessMetadata.cjs +1 -2
- package/dist/harnessMetadata.d.cts +2 -21
- package/dist/harnessMetadata.d.mts +2 -21
- package/dist/harnessMetadata.mjs +1 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +14 -25
- package/dist/index.d.mts +14 -25
- package/dist/index.mjs +1 -1
- package/dist/leaderRegistry.cjs +1 -2
- package/dist/leaderRegistry.d.cts +2 -58
- package/dist/leaderRegistry.d.mts +2 -58
- package/dist/leaderRegistry.mjs +1 -2
- package/dist/logSinkTelemetry.cjs +1 -2
- package/dist/logSinkTelemetry.d.cts +2 -29
- package/dist/logSinkTelemetry.d.mts +2 -29
- package/dist/logSinkTelemetry.mjs +1 -2
- package/dist/rendering.cjs +1 -2
- package/dist/rendering.d.cts +2 -11
- package/dist/rendering.d.mts +2 -11
- package/dist/rendering.mjs +1 -2
- package/dist/services/leader/leaderRegistry.cjs +2 -0
- package/dist/services/leader/leaderRegistry.cjs.map +1 -0
- package/dist/services/leader/leaderRegistry.d.cts +58 -0
- package/dist/services/leader/leaderRegistry.d.cts.map +1 -0
- package/dist/services/leader/leaderRegistry.d.mts +58 -0
- package/dist/services/leader/leaderRegistry.d.mts.map +1 -0
- package/dist/services/leader/leaderRegistry.mjs +2 -0
- package/dist/services/leader/leaderRegistry.mjs.map +1 -0
- package/dist/services/state/uiState.cjs +2 -0
- package/dist/services/state/uiState.cjs.map +1 -0
- package/dist/services/state/uiState.d.cts +38 -0
- package/dist/services/state/uiState.d.cts.map +1 -0
- package/dist/services/state/uiState.d.mts +38 -0
- package/dist/services/state/uiState.d.mts.map +1 -0
- package/dist/services/state/uiState.mjs +2 -0
- package/dist/services/state/uiState.mjs.map +1 -0
- package/dist/services/tools/toolInventory.cjs +2 -0
- package/dist/services/tools/toolInventory.cjs.map +1 -0
- package/dist/services/tools/toolInventory.d.cts +51 -0
- package/dist/services/tools/toolInventory.d.cts.map +1 -0
- package/dist/services/tools/toolInventory.d.mts +51 -0
- package/dist/services/tools/toolInventory.d.mts.map +1 -0
- package/dist/services/tools/toolInventory.mjs +2 -0
- package/dist/services/tools/toolInventory.mjs.map +1 -0
- package/dist/skills.cjs +1 -1
- package/dist/skills.d.cts +2 -1
- package/dist/skills.d.mts +2 -1
- package/dist/skills.mjs +1 -1
- package/dist/theme.cjs +1 -2
- package/dist/theme.d.cts +2 -95
- package/dist/theme.d.mts +2 -95
- package/dist/theme.mjs +1 -2
- package/dist/toolChrome.cjs +1 -0
- package/dist/toolChrome.d.cts +2 -0
- package/dist/toolChrome.d.mts +2 -0
- package/dist/toolChrome.mjs +1 -0
- package/dist/toolInventory.cjs +1 -2
- package/dist/toolInventory.d.cts +2 -40
- package/dist/toolInventory.d.mts +2 -40
- package/dist/toolInventory.mjs +1 -2
- package/dist/tui/builtinToolRender.cjs +14 -0
- package/dist/tui/builtinToolRender.cjs.map +1 -0
- package/dist/tui/builtinToolRender.mjs +14 -0
- package/dist/tui/builtinToolRender.mjs.map +1 -0
- package/dist/tui/codeGutter.cjs +2 -0
- package/dist/tui/codeGutter.cjs.map +1 -0
- package/dist/tui/codeGutter.mjs +2 -0
- package/dist/tui/codeGutter.mjs.map +1 -0
- package/dist/tui/configOverlay.cjs +0 -0
- package/dist/tui/configOverlay.cjs.map +1 -0
- package/dist/tui/configOverlay.d.cts +0 -0
- package/dist/tui/configOverlay.d.cts.map +1 -0
- package/dist/tui/configOverlay.d.mts +0 -0
- package/dist/tui/configOverlay.d.mts.map +1 -0
- package/dist/tui/configOverlay.mjs +0 -0
- package/dist/tui/configOverlay.mjs.map +1 -0
- package/dist/tui/diffRender.cjs +4 -0
- package/dist/tui/diffRender.cjs.map +1 -0
- package/dist/tui/diffRender.mjs +4 -0
- package/dist/tui/diffRender.mjs.map +1 -0
- package/dist/tui/doomEditor.cjs +2 -0
- package/dist/tui/doomEditor.cjs.map +1 -0
- package/dist/tui/doomEditor.d.cts +63 -0
- package/dist/tui/doomEditor.d.cts.map +1 -0
- package/dist/tui/doomEditor.d.mts +63 -0
- package/dist/tui/doomEditor.d.mts.map +1 -0
- package/dist/tui/doomEditor.mjs +2 -0
- package/dist/tui/doomEditor.mjs.map +1 -0
- package/dist/tui/doomFooter.cjs +2 -0
- package/dist/tui/doomFooter.cjs.map +1 -0
- package/dist/tui/doomFooter.d.cts +32 -0
- package/dist/tui/doomFooter.d.cts.map +1 -0
- package/dist/tui/doomFooter.d.mts +32 -0
- package/dist/tui/doomFooter.d.mts.map +1 -0
- package/dist/tui/doomFooter.mjs +2 -0
- package/dist/tui/doomFooter.mjs.map +1 -0
- package/dist/tui/doomHeader.cjs +2 -0
- package/dist/tui/doomHeader.cjs.map +1 -0
- package/dist/tui/doomHeader.d.cts +34 -0
- package/dist/tui/doomHeader.d.cts.map +1 -0
- package/dist/tui/doomHeader.d.mts +34 -0
- package/dist/tui/doomHeader.d.mts.map +1 -0
- package/dist/tui/doomHeader.mjs +2 -0
- package/dist/tui/doomHeader.mjs.map +1 -0
- package/dist/tui/doomOverlay.cjs +2 -0
- package/dist/tui/doomOverlay.cjs.map +1 -0
- package/dist/tui/doomOverlay.d.cts +84 -0
- package/dist/tui/doomOverlay.d.cts.map +1 -0
- package/dist/tui/doomOverlay.d.mts +84 -0
- package/dist/tui/doomOverlay.d.mts.map +1 -0
- package/dist/tui/doomOverlay.mjs +2 -0
- package/dist/tui/doomOverlay.mjs.map +1 -0
- package/dist/tui/leaderHints.cjs +2 -0
- package/dist/tui/leaderHints.cjs.map +1 -0
- package/dist/tui/leaderHints.d.cts +36 -0
- package/dist/tui/leaderHints.d.cts.map +1 -0
- package/dist/tui/leaderHints.d.mts +36 -0
- package/dist/tui/leaderHints.d.mts.map +1 -0
- package/dist/tui/leaderHints.mjs +2 -0
- package/dist/tui/leaderHints.mjs.map +1 -0
- package/dist/tui/rendering.cjs +2 -0
- package/dist/tui/rendering.cjs.map +1 -0
- package/dist/tui/rendering.d.cts +16 -0
- package/dist/tui/rendering.d.cts.map +1 -0
- package/dist/tui/rendering.d.mts +16 -0
- package/dist/tui/rendering.d.mts.map +1 -0
- package/dist/tui/rendering.mjs +2 -0
- package/dist/tui/rendering.mjs.map +1 -0
- package/dist/tui/theme.cjs +2 -0
- package/dist/tui/theme.cjs.map +1 -0
- package/dist/tui/theme.d.cts +103 -0
- package/dist/tui/theme.d.cts.map +1 -0
- package/dist/tui/theme.d.mts +103 -0
- package/dist/tui/theme.d.mts.map +1 -0
- package/dist/tui/theme.mjs +2 -0
- package/dist/tui/theme.mjs.map +1 -0
- package/dist/tui/toolChrome.cjs +2 -0
- package/dist/tui/toolChrome.cjs.map +1 -0
- package/dist/tui/toolChrome.d.cts +30 -0
- package/dist/tui/toolChrome.d.cts.map +1 -0
- package/dist/tui/toolChrome.d.mts +30 -0
- package/dist/tui/toolChrome.d.mts.map +1 -0
- package/dist/tui/toolChrome.mjs +2 -0
- package/dist/tui/toolChrome.mjs.map +1 -0
- package/dist/tui/toolsOverlay.cjs +2 -0
- package/dist/tui/toolsOverlay.cjs.map +1 -0
- package/dist/tui/toolsOverlay.d.cts +29 -0
- package/dist/tui/toolsOverlay.d.cts.map +1 -0
- package/dist/tui/toolsOverlay.d.mts +29 -0
- package/dist/tui/toolsOverlay.d.mts.map +1 -0
- package/dist/tui/toolsOverlay.mjs +2 -0
- package/dist/tui/toolsOverlay.mjs.map +1 -0
- package/dist/types/harnessMetadata.cjs +2 -0
- package/dist/types/harnessMetadata.cjs.map +1 -0
- package/dist/types/harnessMetadata.d.cts +21 -0
- package/dist/types/harnessMetadata.d.cts.map +1 -0
- package/dist/types/harnessMetadata.d.mts +21 -0
- package/dist/types/harnessMetadata.d.mts.map +1 -0
- package/dist/types/harnessMetadata.mjs +2 -0
- package/dist/types/harnessMetadata.mjs.map +1 -0
- package/dist/uiState.cjs +1 -2
- package/dist/uiState.d.cts +2 -37
- package/dist/uiState.d.mts +2 -37
- package/dist/uiState.mjs +1 -2
- package/package.json +19 -12
- package/themes/doom-pi-dark.json +32 -30
- package/dist/_virtual/_rolldown/runtime.mjs +0 -1
- package/dist/components/configOverlay.cjs.map +0 -1
- package/dist/components/configOverlay.d.cts.map +0 -1
- package/dist/components/configOverlay.d.mts.map +0 -1
- package/dist/components/configOverlay.mjs.map +0 -1
- package/dist/components/doomEditor.cjs.map +0 -1
- package/dist/components/doomEditor.d.cts.map +0 -1
- package/dist/components/doomEditor.d.mts.map +0 -1
- package/dist/components/doomEditor.mjs.map +0 -1
- package/dist/components/doomFooter.cjs.map +0 -1
- package/dist/components/doomFooter.d.cts.map +0 -1
- package/dist/components/doomFooter.d.mts.map +0 -1
- package/dist/components/doomFooter.mjs.map +0 -1
- package/dist/components/doomHeader.cjs.map +0 -1
- package/dist/components/doomHeader.d.cts.map +0 -1
- package/dist/components/doomHeader.d.mts.map +0 -1
- package/dist/components/doomHeader.mjs.map +0 -1
- package/dist/components/doomOverlay.cjs.map +0 -1
- package/dist/components/doomOverlay.d.cts.map +0 -1
- package/dist/components/doomOverlay.d.mts.map +0 -1
- package/dist/components/doomOverlay.mjs.map +0 -1
- package/dist/components/leaderHints.cjs.map +0 -1
- package/dist/components/leaderHints.d.cts.map +0 -1
- package/dist/components/leaderHints.d.mts.map +0 -1
- package/dist/components/leaderHints.mjs.map +0 -1
- package/dist/components/toolsOverlay.cjs.map +0 -1
- package/dist/components/toolsOverlay.d.cts.map +0 -1
- package/dist/components/toolsOverlay.d.mts.map +0 -1
- package/dist/components/toolsOverlay.mjs.map +0 -1
- package/dist/config.cjs.map +0 -1
- package/dist/config.d.cts.map +0 -1
- package/dist/config.d.mts.map +0 -1
- package/dist/config.mjs.map +0 -1
- package/dist/extension.cjs.map +0 -1
- package/dist/extension.d.cts.map +0 -1
- package/dist/extension.d.mts.map +0 -1
- package/dist/extension.mjs.map +0 -1
- package/dist/extensionName.cjs.map +0 -1
- package/dist/extensionName.d.cts.map +0 -1
- package/dist/extensionName.d.mts.map +0 -1
- package/dist/extensionName.mjs.map +0 -1
- package/dist/footer.cjs.map +0 -1
- package/dist/footer.d.cts.map +0 -1
- package/dist/footer.d.mts.map +0 -1
- package/dist/footer.mjs.map +0 -1
- package/dist/harnessMetadata.cjs.map +0 -1
- package/dist/harnessMetadata.d.cts.map +0 -1
- package/dist/harnessMetadata.d.mts.map +0 -1
- package/dist/harnessMetadata.mjs.map +0 -1
- package/dist/leaderRegistry.cjs.map +0 -1
- package/dist/leaderRegistry.d.cts.map +0 -1
- package/dist/leaderRegistry.d.mts.map +0 -1
- package/dist/leaderRegistry.mjs.map +0 -1
- package/dist/logSinkTelemetry.cjs.map +0 -1
- package/dist/logSinkTelemetry.d.cts.map +0 -1
- package/dist/logSinkTelemetry.d.mts.map +0 -1
- package/dist/logSinkTelemetry.mjs.map +0 -1
- package/dist/mode.cjs +0 -2
- package/dist/mode.cjs.map +0 -1
- package/dist/mode.d.cts +0 -5
- package/dist/mode.d.cts.map +0 -1
- package/dist/mode.d.mts +0 -5
- package/dist/mode.d.mts.map +0 -1
- package/dist/mode.mjs +0 -2
- package/dist/mode.mjs.map +0 -1
- package/dist/rendering.cjs.map +0 -1
- package/dist/rendering.d.cts.map +0 -1
- package/dist/rendering.d.mts.map +0 -1
- package/dist/rendering.mjs.map +0 -1
- package/dist/theme.cjs.map +0 -1
- package/dist/theme.d.cts.map +0 -1
- package/dist/theme.d.mts.map +0 -1
- package/dist/theme.mjs.map +0 -1
- package/dist/toolInventory.cjs.map +0 -1
- package/dist/toolInventory.d.cts.map +0 -1
- package/dist/toolInventory.d.mts.map +0 -1
- package/dist/toolInventory.mjs.map +0 -1
- package/dist/uiState.cjs.map +0 -1
- package/dist/uiState.d.cts.map +0 -1
- package/dist/uiState.d.mts.map +0 -1
- package/dist/uiState.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomFooter.cjs","names":["path","visibleWidth","fitLine","padLine"],"sources":["../../src/tui/doomFooter.ts"],"sourcesContent":["import path from 'node:path';\nimport type {\n DoomFooterStatusRegistry,\n FooterStatusPlacement,\n FooterTextSegment,\n} from '@agimon-ai/doompi-extension-contracts/footer';\nimport type { ExtensionContext, ReadonlyFooterDataProvider, Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\nimport { type Component, type TUI, visibleWidth } from '@earendil-works/pi-tui';\nimport type { DoomUiState } from '../services/state/uiState.ts';\nimport { fitLine, padLine } from './rendering.ts';\n\nconst NARROW_FOOTER_WIDTH = 90;\nconst SEGMENT_SEPARATOR = ' · ';\nconst DIM_COLOR = 'dim';\nconst MUTED_COLOR = 'muted';\nconst TOOL_PENDING_BACKGROUND = 'toolPendingBg';\nconst DEFAULT_THINKING_LEVEL = 'off';\nconst MODELINE_PLACEMENT = 'modeline' satisfies FooterStatusPlacement;\nconst BEFORE_MODEL_PLACEMENT = 'beforeModel' satisfies FooterStatusPlacement;\n// The theme ships a rising ramp for effort, ending at red for max.\nconst THINKING_COLORS: Record<string, ThemeColor> = {\n off: 'thinkingOff',\n minimal: 'thinkingMinimal',\n low: 'thinkingLow',\n medium: 'thinkingMedium',\n high: 'thinkingHigh',\n xhigh: 'thinkingXhigh',\n max: 'thinkingMax',\n};\nconst CONTEXT_WARN_PERCENT = 60;\nconst CONTEXT_CRITICAL_PERCENT = 85;\n\nfunction normalizeFooterText(text: string): string {\n return text\n .replace(/[\\r\\n\\t]+/gu, ' ')\n .replace(/ {2,}/gu, ' ')\n .trim();\n}\n\nexport class DoomFooter implements Component {\n private readonly unsubscribeBranch: () => void;\n private readonly unsubscribeFooterStatuses: () => void;\n private readonly unsubscribeState: () => void;\n\n constructor(\n tui: TUI,\n private readonly theme: Theme,\n private readonly context: ExtensionContext,\n private readonly footerData: ReadonlyFooterDataProvider,\n private readonly footerStatuses: DoomFooterStatusRegistry,\n private readonly uiState: DoomUiState,\n ) {\n this.unsubscribeBranch = footerData.onBranchChange(() => tui.requestRender());\n this.unsubscribeFooterStatuses = footerStatuses.subscribe(() => tui.requestRender());\n this.unsubscribeState = uiState.subscribe(() => tui.requestRender());\n }\n\n dispose(): void {\n this.unsubscribeBranch();\n this.unsubscribeFooterStatuses();\n this.unsubscribeState();\n }\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0) return [];\n\n const narrow = width < NARROW_FOOTER_WIDTH;\n const snapshot = this.uiState.getSnapshot();\n const modeText = snapshot.active ? snapshot.prefix.join(' ') : narrow ? 'I' : 'INSERT';\n const mode = snapshot.active\n ? this.theme.inverse(this.theme.bold(this.theme.fg('mdHeading', ` ${modeText} `)))\n : this.theme.inverse(this.theme.bold(this.theme.fg('accent', ` ${modeText} `)));\n const repository = path.basename(this.context.cwd) || this.context.cwd;\n const branch = this.footerData.getGitBranch();\n const location = this.locationSegment(repository, branch, narrow);\n const left = `${mode}${location}`;\n const beforeModel = this.footerStatusSummary(BEFORE_MODEL_PLACEMENT, narrow);\n const baseRuntime = this.runtimeSegment(narrow);\n const runtimeWithBeforeModel = this.runtimeSegment(narrow, beforeModel);\n const runtime =\n beforeModel && visibleWidth(mode) + visibleWidth(runtimeWithBeforeModel) <= width\n ? runtimeWithBeforeModel\n : baseRuntime;\n\n if (visibleWidth(left) + visibleWidth(runtime) > width) {\n return [fitLine(`${mode}${runtime}`, width)];\n }\n\n const middleWidth = width - visibleWidth(left) - visibleWidth(runtime);\n const middleText = this.modelineSummary(Math.max(0, middleWidth - 1), narrow);\n const middle = this.theme.bg(TOOL_PENDING_BACKGROUND, padLine(` ${middleText}`, middleWidth));\n return [fitLine(`${left}${middle}${runtime}`, width)];\n }\n\n private locationSegment(repository: string, branch: string | null, narrow: boolean): string {\n const repositoryPart = this.theme.bg(TOOL_PENDING_BACKGROUND, this.theme.fg('text', ` ${repository}`));\n if (!branch) return `${repositoryPart}${this.theme.bg(TOOL_PENDING_BACKGROUND, ' ')}`;\n const branchPrefix = narrow ? ' ' : ' ';\n return `${repositoryPart}${this.theme.bg(\n TOOL_PENDING_BACKGROUND,\n this.theme.fg('success', `${branchPrefix}${branch} `),\n )}`;\n }\n\n private runtimeSegment(narrow: boolean, beforeModel = ''): string {\n const contextPercent = this.context.getContextUsage()?.percent;\n const contextUsage =\n contextPercent === null || contextPercent === undefined\n ? narrow\n ? '?'\n : 'ctx ?'\n : narrow\n ? `${Math.round(contextPercent)}%`\n : `ctx ${Math.round(contextPercent)}%`;\n const modelLimit = narrow ? 18 : 30;\n const model = fitLine(this.compactModel(this.context.model?.id ?? 'no-model'), modelLimit);\n const thinking = this.context.thinkingLevel ?? DEFAULT_THINKING_LEVEL;\n const content = [\n ...(beforeModel ? [beforeModel] : []),\n this.theme.fg('warning', model),\n this.theme.fg(this.thinkingColor(thinking), thinking),\n this.theme.fg(this.contextColor(contextPercent), contextUsage),\n ].join(SEGMENT_SEPARATOR);\n return this.theme.bg(TOOL_PENDING_BACKGROUND, ` ${content} `);\n }\n\n /** Effort rides the theme's rising ramp, from grey at off to red at max. */\n private thinkingColor(level: string): ThemeColor {\n return THINKING_COLORS[level] ?? 'thinkingText';\n }\n\n /** Context usage warms as the window fills, so pressure is visible at a glance. */\n private contextColor(percent: number | null | undefined): ThemeColor {\n if (percent === null || percent === undefined) return MUTED_COLOR;\n if (percent >= CONTEXT_CRITICAL_PERCENT) return 'error';\n if (percent >= CONTEXT_WARN_PERCENT) return 'warning';\n return MUTED_COLOR;\n }\n\n private modelineSummary(availableWidth: number, compact: boolean): string {\n if (availableWidth <= 0) return '';\n return fitLine(this.footerStatusSummary(MODELINE_PLACEMENT, compact), availableWidth);\n }\n\n private footerStatusSummary(placement: FooterStatusPlacement, compact: boolean): string {\n return this.footerStatuses\n .getStatuses()\n .filter((status) => (status.placement ?? MODELINE_PLACEMENT) === placement)\n .map((status) => {\n const segments = compact ? status.compactSegments : status.fullSegments;\n if (segments?.length) return this.renderFooterSegments(segments);\n return this.theme.fg(DIM_COLOR, normalizeFooterText(compact ? status.compactText : status.fullText));\n })\n .filter(Boolean)\n .join(this.theme.fg(DIM_COLOR, SEGMENT_SEPARATOR));\n }\n\n private renderFooterSegments(segments: readonly FooterTextSegment[]): string {\n return segments\n .map((segment) => {\n const text = segment.text.replace(/[\\r\\n\\t]+/gu, ' ');\n return this.theme.fg((segment.color ?? DIM_COLOR) as ThemeColor, text);\n })\n .join('');\n }\n\n private compactModel(model: string): string {\n const modelId = model.split('/').at(-1) ?? model;\n return modelId.replace(/^claude-/, '');\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DoomUiState } from "../services/state/uiState.cjs";
|
|
2
|
+
import { ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Component, TUI } from "@earendil-works/pi-tui";
|
|
4
|
+
import { DoomFooterStatusRegistry } from "@agimon-ai/doompi-extension-contracts/footer";
|
|
5
|
+
//#region src/tui/doomFooter.d.ts
|
|
6
|
+
declare class DoomFooter implements Component {
|
|
7
|
+
private readonly theme;
|
|
8
|
+
private readonly context;
|
|
9
|
+
private readonly footerData;
|
|
10
|
+
private readonly footerStatuses;
|
|
11
|
+
private readonly uiState;
|
|
12
|
+
private readonly unsubscribeBranch;
|
|
13
|
+
private readonly unsubscribeFooterStatuses;
|
|
14
|
+
private readonly unsubscribeState;
|
|
15
|
+
constructor(tui: TUI, theme: Theme, context: ExtensionContext, footerData: ReadonlyFooterDataProvider, footerStatuses: DoomFooterStatusRegistry, uiState: DoomUiState);
|
|
16
|
+
dispose(): void;
|
|
17
|
+
invalidate(): void;
|
|
18
|
+
render(width: number): string[];
|
|
19
|
+
private locationSegment;
|
|
20
|
+
private runtimeSegment;
|
|
21
|
+
/** Effort rides the theme's rising ramp, from grey at off to red at max. */
|
|
22
|
+
private thinkingColor;
|
|
23
|
+
/** Context usage warms as the window fills, so pressure is visible at a glance. */
|
|
24
|
+
private contextColor;
|
|
25
|
+
private modelineSummary;
|
|
26
|
+
private footerStatusSummary;
|
|
27
|
+
private renderFooterSegments;
|
|
28
|
+
private compactModel;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { DoomFooter };
|
|
32
|
+
//# sourceMappingURL=doomFooter.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomFooter.d.cts","names":[],"sources":["../../src/tui/doomFooter.ts"],"mappings":";;;;;cAuCa,sBAAsB;mBAOd;mBACA;mBACA;mBACA;mBACA;mBAVF;mBACA;mBACA;EAGf,YAAA,KAAK,KACY,OAAO,OACP,SAAS,kBACT,YAAY,4BACZ,gBAAgB,0BAChB,SAAS;EAO5B;EAMA;EAEA,OAAO;UA+BC;UAUA;;UAuBA;;UAKA;UAOA;UAKA;UAaA;UASA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DoomUiState } from "../services/state/uiState.mjs";
|
|
2
|
+
import { DoomFooterStatusRegistry } from "@agimon-ai/doompi-extension-contracts/footer";
|
|
3
|
+
import { Component, TUI } from "@earendil-works/pi-tui";
|
|
4
|
+
import { ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
//#region src/tui/doomFooter.d.ts
|
|
6
|
+
declare class DoomFooter implements Component {
|
|
7
|
+
private readonly theme;
|
|
8
|
+
private readonly context;
|
|
9
|
+
private readonly footerData;
|
|
10
|
+
private readonly footerStatuses;
|
|
11
|
+
private readonly uiState;
|
|
12
|
+
private readonly unsubscribeBranch;
|
|
13
|
+
private readonly unsubscribeFooterStatuses;
|
|
14
|
+
private readonly unsubscribeState;
|
|
15
|
+
constructor(tui: TUI, theme: Theme, context: ExtensionContext, footerData: ReadonlyFooterDataProvider, footerStatuses: DoomFooterStatusRegistry, uiState: DoomUiState);
|
|
16
|
+
dispose(): void;
|
|
17
|
+
invalidate(): void;
|
|
18
|
+
render(width: number): string[];
|
|
19
|
+
private locationSegment;
|
|
20
|
+
private runtimeSegment;
|
|
21
|
+
/** Effort rides the theme's rising ramp, from grey at off to red at max. */
|
|
22
|
+
private thinkingColor;
|
|
23
|
+
/** Context usage warms as the window fills, so pressure is visible at a glance. */
|
|
24
|
+
private contextColor;
|
|
25
|
+
private modelineSummary;
|
|
26
|
+
private footerStatusSummary;
|
|
27
|
+
private renderFooterSegments;
|
|
28
|
+
private compactModel;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { DoomFooter };
|
|
32
|
+
//# sourceMappingURL=doomFooter.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomFooter.d.mts","names":[],"sources":["../../src/tui/doomFooter.ts"],"mappings":";;;;;cAuCa,sBAAsB;mBAOd;mBACA;mBACA;mBACA;mBACA;mBAVF;mBACA;mBACA;EAGf,YAAA,KAAK,KACY,OAAO,OACP,SAAS,kBACT,YAAY,4BACZ,gBAAgB,0BAChB,SAAS;EAO5B;EAMA;EAEA,OAAO;UA+BC;UAUA;;UAuBA;;UAKA;UAOA;UAKA;UAaA;UASA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{fitLine as e,padLine as t}from"./rendering.mjs";import n from"node:path";import{visibleWidth as r}from"@earendil-works/pi-tui";const i=` · `,a=`muted`,o=`toolPendingBg`,s=`modeline`,c={off:`thinkingOff`,minimal:`thinkingMinimal`,low:`thinkingLow`,medium:`thinkingMedium`,high:`thinkingHigh`,xhigh:`thinkingXhigh`,max:`thinkingMax`};function l(e){return e.replace(/[\r\n\t]+/gu,` `).replace(/ {2,}/gu,` `).trim()}var u=class{theme;context;footerData;footerStatuses;uiState;unsubscribeBranch;unsubscribeFooterStatuses;unsubscribeState;constructor(e,t,n,r,i,a){this.theme=t,this.context=n,this.footerData=r,this.footerStatuses=i,this.uiState=a,this.unsubscribeBranch=r.onBranchChange(()=>e.requestRender()),this.unsubscribeFooterStatuses=i.subscribe(()=>e.requestRender()),this.unsubscribeState=a.subscribe(()=>e.requestRender())}dispose(){this.unsubscribeBranch(),this.unsubscribeFooterStatuses(),this.unsubscribeState()}invalidate(){}render(i){if(i<=0)return[];let a=i<90,s=this.uiState.getSnapshot(),c=s.active?s.prefix.join(` `):a?`I`:`INSERT`,l=s.active?this.theme.inverse(this.theme.bold(this.theme.fg(`mdHeading`,` ${c} `))):this.theme.inverse(this.theme.bold(this.theme.fg(`accent`,` ${c} `))),u=n.basename(this.context.cwd)||this.context.cwd,d=this.footerData.getGitBranch(),f=`${l}${this.locationSegment(u,d,a)}`,p=this.footerStatusSummary(`beforeModel`,a),m=this.runtimeSegment(a),h=this.runtimeSegment(a,p),g=p&&r(l)+r(h)<=i?h:m;if(r(f)+r(g)>i)return[e(`${l}${g}`,i)];let _=i-r(f)-r(g),v=this.modelineSummary(Math.max(0,_-1),a),y=this.theme.bg(o,t(` ${v}`,_));return[e(`${f}${y}${g}`,i)]}locationSegment(e,t,n){let r=this.theme.bg(o,this.theme.fg(`text`,` ${e}`));if(!t)return`${r}${this.theme.bg(o,` `)}`;let i=n?` `:` `;return`${r}${this.theme.bg(o,this.theme.fg(`success`,`${i}${t} `))}`}runtimeSegment(t,n=``){let r=this.context.getContextUsage()?.percent,a=r==null?t?`?`:`ctx ?`:t?`${Math.round(r)}%`:`ctx ${Math.round(r)}%`,s=t?18:30,c=e(this.compactModel(this.context.model?.id??`no-model`),s),l=this.context.thinkingLevel??`off`,u=[...n?[n]:[],this.theme.fg(`warning`,c),this.theme.fg(this.thinkingColor(l),l),this.theme.fg(this.contextColor(r),a)].join(i);return this.theme.bg(o,` ${u} `)}thinkingColor(e){return c[e]??`thinkingText`}contextColor(e){return e==null?a:e>=85?`error`:e>=60?`warning`:a}modelineSummary(t,n){return t<=0?``:e(this.footerStatusSummary(s,n),t)}footerStatusSummary(e,t){return this.footerStatuses.getStatuses().filter(t=>(t.placement??s)===e).map(e=>{let n=t?e.compactSegments:e.fullSegments;return n?.length?this.renderFooterSegments(n):this.theme.fg(`dim`,l(t?e.compactText:e.fullText))}).filter(Boolean).join(this.theme.fg(`dim`,i))}renderFooterSegments(e){return e.map(e=>{let t=e.text.replace(/[\r\n\t]+/gu,` `);return this.theme.fg(e.color??`dim`,t)}).join(``)}compactModel(e){return(e.split(`/`).at(-1)??e).replace(/^claude-/,``)}};export{u as DoomFooter};
|
|
2
|
+
//# sourceMappingURL=doomFooter.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomFooter.mjs","names":[],"sources":["../../src/tui/doomFooter.ts"],"sourcesContent":["import path from 'node:path';\nimport type {\n DoomFooterStatusRegistry,\n FooterStatusPlacement,\n FooterTextSegment,\n} from '@agimon-ai/doompi-extension-contracts/footer';\nimport type { ExtensionContext, ReadonlyFooterDataProvider, Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\nimport { type Component, type TUI, visibleWidth } from '@earendil-works/pi-tui';\nimport type { DoomUiState } from '../services/state/uiState.ts';\nimport { fitLine, padLine } from './rendering.ts';\n\nconst NARROW_FOOTER_WIDTH = 90;\nconst SEGMENT_SEPARATOR = ' · ';\nconst DIM_COLOR = 'dim';\nconst MUTED_COLOR = 'muted';\nconst TOOL_PENDING_BACKGROUND = 'toolPendingBg';\nconst DEFAULT_THINKING_LEVEL = 'off';\nconst MODELINE_PLACEMENT = 'modeline' satisfies FooterStatusPlacement;\nconst BEFORE_MODEL_PLACEMENT = 'beforeModel' satisfies FooterStatusPlacement;\n// The theme ships a rising ramp for effort, ending at red for max.\nconst THINKING_COLORS: Record<string, ThemeColor> = {\n off: 'thinkingOff',\n minimal: 'thinkingMinimal',\n low: 'thinkingLow',\n medium: 'thinkingMedium',\n high: 'thinkingHigh',\n xhigh: 'thinkingXhigh',\n max: 'thinkingMax',\n};\nconst CONTEXT_WARN_PERCENT = 60;\nconst CONTEXT_CRITICAL_PERCENT = 85;\n\nfunction normalizeFooterText(text: string): string {\n return text\n .replace(/[\\r\\n\\t]+/gu, ' ')\n .replace(/ {2,}/gu, ' ')\n .trim();\n}\n\nexport class DoomFooter implements Component {\n private readonly unsubscribeBranch: () => void;\n private readonly unsubscribeFooterStatuses: () => void;\n private readonly unsubscribeState: () => void;\n\n constructor(\n tui: TUI,\n private readonly theme: Theme,\n private readonly context: ExtensionContext,\n private readonly footerData: ReadonlyFooterDataProvider,\n private readonly footerStatuses: DoomFooterStatusRegistry,\n private readonly uiState: DoomUiState,\n ) {\n this.unsubscribeBranch = footerData.onBranchChange(() => tui.requestRender());\n this.unsubscribeFooterStatuses = footerStatuses.subscribe(() => tui.requestRender());\n this.unsubscribeState = uiState.subscribe(() => tui.requestRender());\n }\n\n dispose(): void {\n this.unsubscribeBranch();\n this.unsubscribeFooterStatuses();\n this.unsubscribeState();\n }\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0) return [];\n\n const narrow = width < NARROW_FOOTER_WIDTH;\n const snapshot = this.uiState.getSnapshot();\n const modeText = snapshot.active ? snapshot.prefix.join(' ') : narrow ? 'I' : 'INSERT';\n const mode = snapshot.active\n ? this.theme.inverse(this.theme.bold(this.theme.fg('mdHeading', ` ${modeText} `)))\n : this.theme.inverse(this.theme.bold(this.theme.fg('accent', ` ${modeText} `)));\n const repository = path.basename(this.context.cwd) || this.context.cwd;\n const branch = this.footerData.getGitBranch();\n const location = this.locationSegment(repository, branch, narrow);\n const left = `${mode}${location}`;\n const beforeModel = this.footerStatusSummary(BEFORE_MODEL_PLACEMENT, narrow);\n const baseRuntime = this.runtimeSegment(narrow);\n const runtimeWithBeforeModel = this.runtimeSegment(narrow, beforeModel);\n const runtime =\n beforeModel && visibleWidth(mode) + visibleWidth(runtimeWithBeforeModel) <= width\n ? runtimeWithBeforeModel\n : baseRuntime;\n\n if (visibleWidth(left) + visibleWidth(runtime) > width) {\n return [fitLine(`${mode}${runtime}`, width)];\n }\n\n const middleWidth = width - visibleWidth(left) - visibleWidth(runtime);\n const middleText = this.modelineSummary(Math.max(0, middleWidth - 1), narrow);\n const middle = this.theme.bg(TOOL_PENDING_BACKGROUND, padLine(` ${middleText}`, middleWidth));\n return [fitLine(`${left}${middle}${runtime}`, width)];\n }\n\n private locationSegment(repository: string, branch: string | null, narrow: boolean): string {\n const repositoryPart = this.theme.bg(TOOL_PENDING_BACKGROUND, this.theme.fg('text', ` ${repository}`));\n if (!branch) return `${repositoryPart}${this.theme.bg(TOOL_PENDING_BACKGROUND, ' ')}`;\n const branchPrefix = narrow ? ' ' : ' ';\n return `${repositoryPart}${this.theme.bg(\n TOOL_PENDING_BACKGROUND,\n this.theme.fg('success', `${branchPrefix}${branch} `),\n )}`;\n }\n\n private runtimeSegment(narrow: boolean, beforeModel = ''): string {\n const contextPercent = this.context.getContextUsage()?.percent;\n const contextUsage =\n contextPercent === null || contextPercent === undefined\n ? narrow\n ? '?'\n : 'ctx ?'\n : narrow\n ? `${Math.round(contextPercent)}%`\n : `ctx ${Math.round(contextPercent)}%`;\n const modelLimit = narrow ? 18 : 30;\n const model = fitLine(this.compactModel(this.context.model?.id ?? 'no-model'), modelLimit);\n const thinking = this.context.thinkingLevel ?? DEFAULT_THINKING_LEVEL;\n const content = [\n ...(beforeModel ? [beforeModel] : []),\n this.theme.fg('warning', model),\n this.theme.fg(this.thinkingColor(thinking), thinking),\n this.theme.fg(this.contextColor(contextPercent), contextUsage),\n ].join(SEGMENT_SEPARATOR);\n return this.theme.bg(TOOL_PENDING_BACKGROUND, ` ${content} `);\n }\n\n /** Effort rides the theme's rising ramp, from grey at off to red at max. */\n private thinkingColor(level: string): ThemeColor {\n return THINKING_COLORS[level] ?? 'thinkingText';\n }\n\n /** Context usage warms as the window fills, so pressure is visible at a glance. */\n private contextColor(percent: number | null | undefined): ThemeColor {\n if (percent === null || percent === undefined) return MUTED_COLOR;\n if (percent >= CONTEXT_CRITICAL_PERCENT) return 'error';\n if (percent >= CONTEXT_WARN_PERCENT) return 'warning';\n return MUTED_COLOR;\n }\n\n private modelineSummary(availableWidth: number, compact: boolean): string {\n if (availableWidth <= 0) return '';\n return fitLine(this.footerStatusSummary(MODELINE_PLACEMENT, compact), availableWidth);\n }\n\n private footerStatusSummary(placement: FooterStatusPlacement, compact: boolean): string {\n return this.footerStatuses\n .getStatuses()\n .filter((status) => (status.placement ?? MODELINE_PLACEMENT) === placement)\n .map((status) => {\n const segments = compact ? status.compactSegments : status.fullSegments;\n if (segments?.length) return this.renderFooterSegments(segments);\n return this.theme.fg(DIM_COLOR, normalizeFooterText(compact ? status.compactText : status.fullText));\n })\n .filter(Boolean)\n .join(this.theme.fg(DIM_COLOR, SEGMENT_SEPARATOR));\n }\n\n private renderFooterSegments(segments: readonly FooterTextSegment[]): string {\n return segments\n .map((segment) => {\n const text = segment.text.replace(/[\\r\\n\\t]+/gu, ' ');\n return this.theme.fg((segment.color ?? DIM_COLOR) as ThemeColor, text);\n })\n .join('');\n }\n\n private compactModel(model: string): string {\n const modelId = model.split('/').at(-1) ?? model;\n return modelId.replace(/^claude-/, '');\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./rendering.cjs"),n=require("../types/harnessMetadata.cjs");let r=require("node:path");r=e.__toESM(r,1);let i=require("@earendil-works/pi-tui");const a=`default`,o=`no-model`,s=`core`,c=`BOOT / 01`,l=`● READY`,u=` · `,d=/^claude-/,f=`REPOSITORY`,p=`PROFILE`,m=`LAYERS`,h=`MODE`,g=`MODEL`,_=`─`.repeat(8),v=`accent`,y=`success`,b=`muted`,x=`text`,S=`mdCode`,C=`warning`,w=`borderMuted`;var T=class{theme;cwd;readMetadata;runtime;getTerminalRows;constructor(e,t,r=n.readDoomHarnessMetadata,i,a=()=>24){this.theme=e,this.cwd=t,this.readMetadata=r,this.runtime=i,this.getTerminalRows=a}invalidate(){}render(e){if(e<=0)return[];if(e<4)return[this.hairline(e)];let t=this.readMetadata();if(this.runtime?.sessionManager.getEntries().some(e=>e.type===`message`)??!1)return[];let n=this.getTerminalRows();if(n<24)return this.renderCompact(e,t);let r=e<90?this.renderNarrowDashboard(e,t):this.renderWideDashboard(e,t),i=this.renderHints(e),a=2+r.length+i.length+2,o=Math.max(3,n-8-a);return[...Array.from({length:2},()=>``),...r,``,...i,``,...this.renderEmptyState(e,o)]}renderWideDashboard(e,n){let r=Math.max(1,e-30-3),i=t.alignLine(this.theme.bold(this.theme.fg(`mdHeading`,`SESSION CONTEXT`)),this.theme.fg(`dim`,`${new Date().toLocaleTimeString(`en-GB`,{hour12:!1})} / local`),r),l=this.compactPath(n.root??this.cwd),d=n.profile??a,_=n.domains.join(`, `)||a,w=n.layers.join(u)||s,T=this.runtime?.model?.id??o,E=this.runtime?.thinkingLevel??`off`,D=`${this.theme.fg(y,`━━━━━━━━`)} ${this.theme.bold(this.theme.fg(y,`READY`))}`;return[this.columns(this.identityCell(this.theme.fg(`dim`,c)),i,e),this.columns(this.identityCell(this.wordmark()),this.contextRow(f,l,x),e),this.columns(this.identityCell(this.theme.fg(b,`Pi, configured for focus.`)),this.contextRow(p,d,S),e),this.columns(this.identityCell(D),this.contextRow(`DOMAINS`,_,v),e),this.columns(this.identityCell(``),this.contextRow(m,w,b),e),this.columns(this.identityCell(``),this.contextRow(h,n.majorMode,y),e),this.columns(this.identityCell(``),this.contextRow(g,`${T} / ${E}`,C),e),this.hairline(e)]}renderNarrowDashboard(e,n){let i=r.default.basename(n.root??this.cwd)||this.cwd,d=n.profile??a,_=n.domains.join(`, `)||a,v=this.runtime?.sessionManager.getSessionId().slice(0,6)??`new`,w=n.layers.join(u)||s,T=this.compactModel(this.runtime?.model?.id??o),E=this.runtime?.thinkingLevel??`off`,D=this.theme.bold(this.theme.fg(y,l));return[...[t.alignLine(this.identityCell(`${this.theme.fg(`dim`,c)} ${this.wordmark()}`),D,e),this.identityCell(this.contextRow(f,i,x)),this.identityCell(this.contextRow(p,`${d} / ${_} / ${v}`,S)),this.identityCell(this.contextRow(m,w,b)),this.identityCell(this.contextRow(h,n.majorMode,y)),this.identityCell(this.contextRow(g,`${T} / ${E}`,C))].map(n=>t.fitLine(n,e)),this.hairline(e)]}renderCompact(e,n){let i=r.default.basename(n.root??this.cwd)||this.cwd,s=n.profile??a,c=n.domains.join(`,`)||a,u=this.compactModel(this.runtime?.model?.id??o),d=this.runtime?.thinkingLevel??`off`,f=this.runtime?.isIdle()===!1?`● ACTIVE`:l;return[t.alignLine(`${this.wordmark()} ${this.theme.bold(this.theme.fg(x,i))}`,this.theme.fg(y,f),e),t.fitLine(`${s} / ${c} / ${n.majorMode}`,e),t.alignLine(this.theme.fg(C,`${u} / ${d}`),this.theme.fg(`dim`,`CTRL+SPACE leader · SPC when empty`),e),this.hairline(e)]}renderHints(e){let n=[this.keyHint(`CTRL+SPACE`,`leader anywhere`),this.keyHint(`SPC`,`when empty`),this.keyHint(`/`,`commands`),this.keyHint(`!`,`shell`)].join(` `);return[t.fitLine(n,e)]}renderEmptyState(e,t){let n=Array.from({length:Math.max(3,t)},()=>``),r=this.theme.fg(w,_),i=`${r} ${this.theme.fg(`dim`,`NO TRANSCRIPT YET`)} ${r}`,a=this.theme.fg(b,`Describe the work below. Pi keeps your existing config and session history.`),o=Math.max(0,Math.floor(n.length/2)-1);return n[o]=this.centerLine(i,e),n.length>o+1&&(n[o+1]=this.centerLine(a,e)),n}hairline(e){return this.theme.fg(w,`─`.repeat(Math.max(0,e)))}centerLine(e,n){let r=t.fitLine(e,n),a=` `.repeat(Math.max(0,Math.floor((n-(0,i.visibleWidth)(r))/2)));return t.fitLine(`${a}${r}`,n)}identityCell(e){let t=this.theme.fg(v,`▌`);return e?`${t} ${e}`:t}columns(e,n,r){let i=Math.max(0,r-30-3);return t.fitLine(`${t.padLine(e,30)}${` `.repeat(3)}${t.fitLine(n,i)}`,r)}contextRow(e,t,n){return`${this.theme.fg(`dim`,e.padEnd(12))}${this.theme.fg(n,t)}`}keyHint(e,t){return`${this.theme.bg(`selectedBg`,this.theme.fg(v,` ${e} `))} ${this.theme.fg(`dim`,t)}`}wordmark(){return`${this.theme.bold(this.theme.fg(v,`DOOM`))} ${this.theme.inverse(this.theme.bold(this.theme.fg(x,` PI `)))}`}compactModel(e){return e.split(`/`).at(-1)?.replace(d,``)??e}compactPath(e){let t=process.env.HOME;return t&&e.startsWith(t)?`~${e.slice(t.length)}`:e}};exports.DoomHeader=T;
|
|
2
|
+
//# sourceMappingURL=doomHeader.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomHeader.cjs","names":["readDoomHarnessMetadata","alignLine","path","fitLine","visibleWidth","padLine"],"sources":["../../src/tui/doomHeader.ts"],"sourcesContent":["import path from 'node:path';\nimport { type ExtensionContext, type Theme, type ThemeColor } from '@earendil-works/pi-coding-agent';\nimport { type Component, visibleWidth } from '@earendil-works/pi-tui';\nimport { type DoomHarnessMetadata, readDoomHarnessMetadata } from '../types/harnessMetadata.ts';\nimport { alignLine, fitLine, padLine } from './rendering.ts';\n\nconst WIDE_HEADER_WIDTH = 90;\nconst FULL_STARTUP_MIN_ROWS = 24;\n// Rows Pi renders below the header: the four-line editor frame, the modeline,\n// and the spacing around them. Under-reserving scrolls the dashboard off the top.\nconst RESERVED_INTERFACE_ROWS = 8;\nconst MIN_EMPTY_STATE_HEIGHT = 3;\nconst IDENTITY_COLUMN_WIDTH = 30;\nconst COLUMN_GAP = 3;\nconst MIN_RENDER_WIDTH = 4;\nconst SECTION_SEPARATOR_ROWS = 2;\n/** Breathing room between the shell prompt Pi launched from and the dashboard. */\nconst TOP_PADDING_ROWS = 2;\nconst CONTEXT_LABEL_WIDTH = 12;\nconst SESSION_ID_PREVIEW_LENGTH = 6;\nconst EMPTY_STATE_RULE_LENGTH = 8;\nconst DEFAULT_TERMINAL_ROWS = 24;\nconst DEFAULT_METADATA_LABEL = 'default';\nconst DEFAULT_MODEL_LABEL = 'no-model';\nconst DEFAULT_THINKING_LABEL = 'off';\nconst DEFAULT_LAYERS_LABEL = 'core';\nconst DEFAULT_SESSION_LABEL = 'new';\nconst BOOT_LABEL = 'BOOT / 01';\nconst READY_STATUS = '● READY';\nconst ACTIVE_STATUS = '● ACTIVE';\nconst EMPTY_STATE_LABEL = 'NO TRANSCRIPT YET';\nconst EMPTY_STATE_GUIDANCE = 'Describe the work below. Pi keeps your existing config and session history.';\nconst PRODUCT_DESCRIPTOR = 'Pi, configured for focus.';\nconst SEGMENT_SEPARATOR = ' · ';\nconst DISPLAY_SEPARATOR = ' / ';\nconst LIST_SEPARATOR = ', ';\nconst MESSAGE_ENTRY_TYPE = 'message';\nconst TIME_LOCALE = 'en-GB';\nconst HOME_ENV = 'HOME';\nconst CLAUDE_MODEL_PREFIX = /^claude-/;\nconst REPOSITORY_LABEL = 'REPOSITORY';\nconst PROFILE_LABEL = 'PROFILE';\nconst DOMAINS_LABEL = 'DOMAINS';\nconst LAYERS_LABEL = 'LAYERS';\nconst MODE_LABEL = 'MODE';\nconst MODEL_LABEL = 'MODEL';\nconst HORIZONTAL_BORDER = '─';\nconst IDENTITY_ACCENT_BAR = '▌';\nconst READY_RULE = '━━━━━━━━';\nconst EMPTY_STATE_RULE = HORIZONTAL_BORDER.repeat(EMPTY_STATE_RULE_LENGTH);\nconst DIM_COLOR: ThemeColor = 'dim';\nconst ACCENT_COLOR: ThemeColor = 'accent';\nconst SUCCESS_COLOR: ThemeColor = 'success';\nconst MUTED_COLOR: ThemeColor = 'muted';\nconst TEXT_COLOR: ThemeColor = 'text';\nconst CODE_COLOR: ThemeColor = 'mdCode';\nconst WARNING_COLOR: ThemeColor = 'warning';\nconst HEADING_COLOR: ThemeColor = 'mdHeading';\nconst SELECTED_BG_COLOR = 'selectedBg';\nconst MUTED_BORDER_COLOR: ThemeColor = 'borderMuted';\n\ntype MetadataReader = () => DoomHarnessMetadata;\ntype TerminalRowsReader = () => number;\ntype HeaderRuntime = Pick<ExtensionContext, 'isIdle' | 'model' | 'sessionManager' | 'thinkingLevel'>;\n\nexport class DoomHeader implements Component {\n constructor(\n private readonly theme: Theme,\n private readonly cwd: string,\n private readonly readMetadata: MetadataReader = readDoomHarnessMetadata,\n private readonly runtime?: HeaderRuntime,\n private readonly getTerminalRows: TerminalRowsReader = () => DEFAULT_TERMINAL_ROWS,\n ) {}\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0) return [];\n if (width < MIN_RENDER_WIDTH) return [this.hairline(width)];\n\n const metadata = this.readMetadata();\n const hasTranscript =\n this.runtime?.sessionManager.getEntries().some((entry) => entry.type === MESSAGE_ENTRY_TYPE) ?? false;\n if (hasTranscript) return [];\n\n const terminalRows = this.getTerminalRows();\n if (terminalRows < FULL_STARTUP_MIN_ROWS) return this.renderCompact(width, metadata);\n\n const dashboard =\n width < WIDE_HEADER_WIDTH\n ? this.renderNarrowDashboard(width, metadata)\n : this.renderWideDashboard(width, metadata);\n const hints = this.renderHints(width);\n const fixedHeaderRows = TOP_PADDING_ROWS + dashboard.length + hints.length + SECTION_SEPARATOR_ROWS;\n const emptyStateHeight = Math.max(MIN_EMPTY_STATE_HEIGHT, terminalRows - RESERVED_INTERFACE_ROWS - fixedHeaderRows);\n const topPadding = Array.from({ length: TOP_PADDING_ROWS }, () => '');\n\n return [...topPadding, ...dashboard, '', ...hints, '', ...this.renderEmptyState(width, emptyStateHeight)];\n }\n\n private renderWideDashboard(width: number, metadata: DoomHarnessMetadata): string[] {\n const contextWidth = Math.max(1, width - IDENTITY_COLUMN_WIDTH - COLUMN_GAP);\n const contextHeading = alignLine(\n this.theme.bold(this.theme.fg(HEADING_COLOR, 'SESSION CONTEXT')),\n this.theme.fg(\n DIM_COLOR,\n `${new Date().toLocaleTimeString(TIME_LOCALE, { hour12: false })}${DISPLAY_SEPARATOR}local`,\n ),\n contextWidth,\n );\n const repository = this.compactPath(metadata.root ?? this.cwd);\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(LIST_SEPARATOR) || DEFAULT_METADATA_LABEL;\n const layers = metadata.layers.join(SEGMENT_SEPARATOR) || DEFAULT_LAYERS_LABEL;\n const model = this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL;\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const ready = `${this.theme.fg(SUCCESS_COLOR, READY_RULE)} ${this.theme.bold(\n this.theme.fg(SUCCESS_COLOR, 'READY'),\n )}`;\n const rows = [\n this.columns(this.identityCell(this.theme.fg(DIM_COLOR, BOOT_LABEL)), contextHeading, width),\n this.columns(\n this.identityCell(this.wordmark()),\n this.contextRow(REPOSITORY_LABEL, repository, TEXT_COLOR),\n width,\n ),\n this.columns(\n this.identityCell(this.theme.fg(MUTED_COLOR, PRODUCT_DESCRIPTOR)),\n this.contextRow(PROFILE_LABEL, profile, CODE_COLOR),\n width,\n ),\n this.columns(this.identityCell(ready), this.contextRow(DOMAINS_LABEL, domains, ACCENT_COLOR), width),\n this.columns(this.identityCell(''), this.contextRow(LAYERS_LABEL, layers, MUTED_COLOR), width),\n this.columns(this.identityCell(''), this.contextRow(MODE_LABEL, metadata.majorMode, SUCCESS_COLOR), width),\n this.columns(\n this.identityCell(''),\n this.contextRow(MODEL_LABEL, `${model}${DISPLAY_SEPARATOR}${thinking}`, WARNING_COLOR),\n width,\n ),\n ];\n\n return [...rows, this.hairline(width)];\n }\n\n private renderNarrowDashboard(width: number, metadata: DoomHarnessMetadata): string[] {\n const repository = path.basename(metadata.root ?? this.cwd) || this.cwd;\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(LIST_SEPARATOR) || DEFAULT_METADATA_LABEL;\n const session =\n this.runtime?.sessionManager.getSessionId().slice(0, SESSION_ID_PREVIEW_LENGTH) ?? DEFAULT_SESSION_LABEL;\n const layers = metadata.layers.join(SEGMENT_SEPARATOR) || DEFAULT_LAYERS_LABEL;\n const model = this.compactModel(this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL);\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const ready = this.theme.bold(this.theme.fg(SUCCESS_COLOR, READY_STATUS));\n const rows = [\n alignLine(this.identityCell(`${this.theme.fg(DIM_COLOR, BOOT_LABEL)} ${this.wordmark()}`), ready, width),\n this.identityCell(this.contextRow(REPOSITORY_LABEL, repository, TEXT_COLOR)),\n this.identityCell(\n this.contextRow(\n PROFILE_LABEL,\n `${profile}${DISPLAY_SEPARATOR}${domains}${DISPLAY_SEPARATOR}${session}`,\n CODE_COLOR,\n ),\n ),\n this.identityCell(this.contextRow(LAYERS_LABEL, layers, MUTED_COLOR)),\n this.identityCell(this.contextRow(MODE_LABEL, metadata.majorMode, SUCCESS_COLOR)),\n this.identityCell(this.contextRow(MODEL_LABEL, `${model}${DISPLAY_SEPARATOR}${thinking}`, WARNING_COLOR)),\n ].map((row) => fitLine(row, width));\n\n return [...rows, this.hairline(width)];\n }\n\n private renderCompact(width: number, metadata: DoomHarnessMetadata): string[] {\n const repository = path.basename(metadata.root ?? this.cwd) || this.cwd;\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(',') || DEFAULT_METADATA_LABEL;\n const model = this.compactModel(this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL);\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const activity = this.runtime?.isIdle() === false ? ACTIVE_STATUS : READY_STATUS;\n const rows = [\n alignLine(\n `${this.wordmark()} ${this.theme.bold(this.theme.fg(TEXT_COLOR, repository))}`,\n this.theme.fg(SUCCESS_COLOR, activity),\n width,\n ),\n fitLine(`${profile}${DISPLAY_SEPARATOR}${domains}${DISPLAY_SEPARATOR}${metadata.majorMode}`, width),\n alignLine(\n this.theme.fg(WARNING_COLOR, `${model}${DISPLAY_SEPARATOR}${thinking}`),\n this.theme.fg(DIM_COLOR, 'CTRL+SPACE leader · SPC when empty'),\n width,\n ),\n ];\n\n return [...rows, this.hairline(width)];\n }\n\n private renderHints(width: number): string[] {\n const content = [\n this.keyHint('CTRL+SPACE', 'leader anywhere'),\n this.keyHint('SPC', 'when empty'),\n this.keyHint('/', 'commands'),\n this.keyHint('!', 'shell'),\n ].join(' ');\n return [fitLine(content, width)];\n }\n\n private renderEmptyState(width: number, height: number): string[] {\n const lines = Array.from({ length: Math.max(MIN_EMPTY_STATE_HEIGHT, height) }, () => '');\n const rule = this.theme.fg(MUTED_BORDER_COLOR, EMPTY_STATE_RULE);\n const label = `${rule} ${this.theme.fg(DIM_COLOR, EMPTY_STATE_LABEL)} ${rule}`;\n const guidance = this.theme.fg(MUTED_COLOR, EMPTY_STATE_GUIDANCE);\n const labelIndex = Math.max(0, Math.floor(lines.length / SECTION_SEPARATOR_ROWS) - 1);\n lines[labelIndex] = this.centerLine(label, width);\n if (lines.length > labelIndex + 1) lines[labelIndex + 1] = this.centerLine(guidance, width);\n\n return lines;\n }\n\n private hairline(width: number): string {\n return this.theme.fg(MUTED_BORDER_COLOR, HORIZONTAL_BORDER.repeat(Math.max(0, width)));\n }\n\n private centerLine(content: string, width: number): string {\n const fitted = fitLine(content, width);\n const leftPadding = ' '.repeat(Math.max(0, Math.floor((width - visibleWidth(fitted)) / SECTION_SEPARATOR_ROWS)));\n return fitLine(`${leftPadding}${fitted}`, width);\n }\n\n private identityCell(content: string): string {\n const bar = this.theme.fg(ACCENT_COLOR, IDENTITY_ACCENT_BAR);\n return content ? `${bar} ${content}` : bar;\n }\n\n private columns(left: string, right: string, width: number): string {\n const rightWidth = Math.max(0, width - IDENTITY_COLUMN_WIDTH - COLUMN_GAP);\n return fitLine(\n `${padLine(left, IDENTITY_COLUMN_WIDTH)}${' '.repeat(COLUMN_GAP)}${fitLine(right, rightWidth)}`,\n width,\n );\n }\n\n private contextRow(label: string, value: string, color: ThemeColor): string {\n return `${this.theme.fg(DIM_COLOR, label.padEnd(CONTEXT_LABEL_WIDTH))}${this.theme.fg(color, value)}`;\n }\n\n private keyHint(key: string, label: string): string {\n const badge = this.theme.bg(SELECTED_BG_COLOR, this.theme.fg(ACCENT_COLOR, ` ${key} `));\n return `${badge} ${this.theme.fg(DIM_COLOR, label)}`;\n }\n\n private wordmark(): string {\n return `${this.theme.bold(this.theme.fg(ACCENT_COLOR, 'DOOM'))} ${this.theme.inverse(\n this.theme.bold(this.theme.fg(TEXT_COLOR, ' PI ')),\n )}`;\n }\n\n private compactModel(model: string): string {\n return model.split('/').at(-1)?.replace(CLAUDE_MODEL_PREFIX, '') ?? model;\n }\n\n private compactPath(value: string): string {\n const home = process.env[HOME_ENV];\n return home && value.startsWith(home) ? `~${value.slice(home.length)}` : value;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DoomHarnessMetadata } from "../types/harnessMetadata.cjs";
|
|
2
|
+
import { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Component } from "@earendil-works/pi-tui";
|
|
4
|
+
//#region src/tui/doomHeader.d.ts
|
|
5
|
+
type MetadataReader = () => DoomHarnessMetadata;
|
|
6
|
+
type TerminalRowsReader = () => number;
|
|
7
|
+
type HeaderRuntime = Pick<ExtensionContext, 'isIdle' | 'model' | 'sessionManager' | 'thinkingLevel'>;
|
|
8
|
+
declare class DoomHeader implements Component {
|
|
9
|
+
private readonly theme;
|
|
10
|
+
private readonly cwd;
|
|
11
|
+
private readonly readMetadata;
|
|
12
|
+
private readonly runtime?;
|
|
13
|
+
private readonly getTerminalRows;
|
|
14
|
+
constructor(theme: Theme, cwd: string, readMetadata?: MetadataReader, runtime?: HeaderRuntime | undefined, getTerminalRows?: TerminalRowsReader);
|
|
15
|
+
invalidate(): void;
|
|
16
|
+
render(width: number): string[];
|
|
17
|
+
private renderWideDashboard;
|
|
18
|
+
private renderNarrowDashboard;
|
|
19
|
+
private renderCompact;
|
|
20
|
+
private renderHints;
|
|
21
|
+
private renderEmptyState;
|
|
22
|
+
private hairline;
|
|
23
|
+
private centerLine;
|
|
24
|
+
private identityCell;
|
|
25
|
+
private columns;
|
|
26
|
+
private contextRow;
|
|
27
|
+
private keyHint;
|
|
28
|
+
private wordmark;
|
|
29
|
+
private compactModel;
|
|
30
|
+
private compactPath;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { DoomHeader };
|
|
34
|
+
//# sourceMappingURL=doomHeader.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomHeader.d.cts","names":[],"sources":["../../src/tui/doomHeader.ts"],"mappings":";;;;KA6DK,uBAAuB;KACvB;KACA,gBAAgB,KAAK;cAEb,sBAAsB;mBAEd;mBACA;mBACA;mBACA;mBACA;EAJA,YAAA,OAAO,OACP,aACA,eAAc,gBACd,UAAU,2BACV,kBAAiB;EAGpC;EAEA,OAAO;UAwBC;UA4CA;UA4BA;UAwBA;UAUA;UAYA;UAIA;UAMA;UAKA;UAQA;UAIA;UAKA;UAMA;UAIA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DoomHarnessMetadata } from "../types/harnessMetadata.mjs";
|
|
2
|
+
import { Component } from "@earendil-works/pi-tui";
|
|
3
|
+
import { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
//#region src/tui/doomHeader.d.ts
|
|
5
|
+
type MetadataReader = () => DoomHarnessMetadata;
|
|
6
|
+
type TerminalRowsReader = () => number;
|
|
7
|
+
type HeaderRuntime = Pick<ExtensionContext, 'isIdle' | 'model' | 'sessionManager' | 'thinkingLevel'>;
|
|
8
|
+
declare class DoomHeader implements Component {
|
|
9
|
+
private readonly theme;
|
|
10
|
+
private readonly cwd;
|
|
11
|
+
private readonly readMetadata;
|
|
12
|
+
private readonly runtime?;
|
|
13
|
+
private readonly getTerminalRows;
|
|
14
|
+
constructor(theme: Theme, cwd: string, readMetadata?: MetadataReader, runtime?: HeaderRuntime | undefined, getTerminalRows?: TerminalRowsReader);
|
|
15
|
+
invalidate(): void;
|
|
16
|
+
render(width: number): string[];
|
|
17
|
+
private renderWideDashboard;
|
|
18
|
+
private renderNarrowDashboard;
|
|
19
|
+
private renderCompact;
|
|
20
|
+
private renderHints;
|
|
21
|
+
private renderEmptyState;
|
|
22
|
+
private hairline;
|
|
23
|
+
private centerLine;
|
|
24
|
+
private identityCell;
|
|
25
|
+
private columns;
|
|
26
|
+
private contextRow;
|
|
27
|
+
private keyHint;
|
|
28
|
+
private wordmark;
|
|
29
|
+
private compactModel;
|
|
30
|
+
private compactPath;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { DoomHeader };
|
|
34
|
+
//# sourceMappingURL=doomHeader.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomHeader.d.mts","names":[],"sources":["../../src/tui/doomHeader.ts"],"mappings":";;;;KA6DK,uBAAuB;KACvB;KACA,gBAAgB,KAAK;cAEb,sBAAsB;mBAEd;mBACA;mBACA;mBACA;mBACA;EAJA,YAAA,OAAO,OACP,aACA,eAAc,gBACd,UAAU,2BACV,kBAAiB;EAGpC;EAEA,OAAO;UAwBC;UA4CA;UA4BA;UAwBA;UAUA;UAYA;UAIA;UAMA;UAKA;UAQA;UAIA;UAKA;UAMA;UAIA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{alignLine as e,fitLine as t,padLine as n}from"./rendering.mjs";import{readDoomHarnessMetadata as r}from"../types/harnessMetadata.mjs";import i from"node:path";import{visibleWidth as a}from"@earendil-works/pi-tui";const o=`default`,s=`no-model`,c=`core`,l=`BOOT / 01`,u=`● READY`,d=` · `,f=/^claude-/,p=`REPOSITORY`,m=`PROFILE`,h=`LAYERS`,g=`MODE`,_=`MODEL`,v=`─`.repeat(8),y=`accent`,b=`success`,x=`muted`,S=`text`,C=`mdCode`,w=`warning`,T=`borderMuted`;var E=class{theme;cwd;readMetadata;runtime;getTerminalRows;constructor(e,t,n=r,i,a=()=>24){this.theme=e,this.cwd=t,this.readMetadata=n,this.runtime=i,this.getTerminalRows=a}invalidate(){}render(e){if(e<=0)return[];if(e<4)return[this.hairline(e)];let t=this.readMetadata();if(this.runtime?.sessionManager.getEntries().some(e=>e.type===`message`)??!1)return[];let n=this.getTerminalRows();if(n<24)return this.renderCompact(e,t);let r=e<90?this.renderNarrowDashboard(e,t):this.renderWideDashboard(e,t),i=this.renderHints(e),a=2+r.length+i.length+2,o=Math.max(3,n-8-a);return[...Array.from({length:2},()=>``),...r,``,...i,``,...this.renderEmptyState(e,o)]}renderWideDashboard(t,n){let r=Math.max(1,t-30-3),i=e(this.theme.bold(this.theme.fg(`mdHeading`,`SESSION CONTEXT`)),this.theme.fg(`dim`,`${new Date().toLocaleTimeString(`en-GB`,{hour12:!1})} / local`),r),a=this.compactPath(n.root??this.cwd),u=n.profile??o,f=n.domains.join(`, `)||o,v=n.layers.join(d)||c,T=this.runtime?.model?.id??s,E=this.runtime?.thinkingLevel??`off`,D=`${this.theme.fg(b,`━━━━━━━━`)} ${this.theme.bold(this.theme.fg(b,`READY`))}`;return[this.columns(this.identityCell(this.theme.fg(`dim`,l)),i,t),this.columns(this.identityCell(this.wordmark()),this.contextRow(p,a,S),t),this.columns(this.identityCell(this.theme.fg(x,`Pi, configured for focus.`)),this.contextRow(m,u,C),t),this.columns(this.identityCell(D),this.contextRow(`DOMAINS`,f,y),t),this.columns(this.identityCell(``),this.contextRow(h,v,x),t),this.columns(this.identityCell(``),this.contextRow(g,n.majorMode,b),t),this.columns(this.identityCell(``),this.contextRow(_,`${T} / ${E}`,w),t),this.hairline(t)]}renderNarrowDashboard(n,r){let a=i.basename(r.root??this.cwd)||this.cwd,f=r.profile??o,v=r.domains.join(`, `)||o,y=this.runtime?.sessionManager.getSessionId().slice(0,6)??`new`,T=r.layers.join(d)||c,E=this.compactModel(this.runtime?.model?.id??s),D=this.runtime?.thinkingLevel??`off`,O=this.theme.bold(this.theme.fg(b,u));return[...[e(this.identityCell(`${this.theme.fg(`dim`,l)} ${this.wordmark()}`),O,n),this.identityCell(this.contextRow(p,a,S)),this.identityCell(this.contextRow(m,`${f} / ${v} / ${y}`,C)),this.identityCell(this.contextRow(h,T,x)),this.identityCell(this.contextRow(g,r.majorMode,b)),this.identityCell(this.contextRow(_,`${E} / ${D}`,w))].map(e=>t(e,n)),this.hairline(n)]}renderCompact(n,r){let a=i.basename(r.root??this.cwd)||this.cwd,c=r.profile??o,l=r.domains.join(`,`)||o,d=this.compactModel(this.runtime?.model?.id??s),f=this.runtime?.thinkingLevel??`off`,p=this.runtime?.isIdle()===!1?`● ACTIVE`:u;return[e(`${this.wordmark()} ${this.theme.bold(this.theme.fg(S,a))}`,this.theme.fg(b,p),n),t(`${c} / ${l} / ${r.majorMode}`,n),e(this.theme.fg(w,`${d} / ${f}`),this.theme.fg(`dim`,`CTRL+SPACE leader · SPC when empty`),n),this.hairline(n)]}renderHints(e){let n=[this.keyHint(`CTRL+SPACE`,`leader anywhere`),this.keyHint(`SPC`,`when empty`),this.keyHint(`/`,`commands`),this.keyHint(`!`,`shell`)].join(` `);return[t(n,e)]}renderEmptyState(e,t){let n=Array.from({length:Math.max(3,t)},()=>``),r=this.theme.fg(T,v),i=`${r} ${this.theme.fg(`dim`,`NO TRANSCRIPT YET`)} ${r}`,a=this.theme.fg(x,`Describe the work below. Pi keeps your existing config and session history.`),o=Math.max(0,Math.floor(n.length/2)-1);return n[o]=this.centerLine(i,e),n.length>o+1&&(n[o+1]=this.centerLine(a,e)),n}hairline(e){return this.theme.fg(T,`─`.repeat(Math.max(0,e)))}centerLine(e,n){let r=t(e,n),i=` `.repeat(Math.max(0,Math.floor((n-a(r))/2)));return t(`${i}${r}`,n)}identityCell(e){let t=this.theme.fg(y,`▌`);return e?`${t} ${e}`:t}columns(e,r,i){let a=Math.max(0,i-30-3);return t(`${n(e,30)}${` `.repeat(3)}${t(r,a)}`,i)}contextRow(e,t,n){return`${this.theme.fg(`dim`,e.padEnd(12))}${this.theme.fg(n,t)}`}keyHint(e,t){return`${this.theme.bg(`selectedBg`,this.theme.fg(y,` ${e} `))} ${this.theme.fg(`dim`,t)}`}wordmark(){return`${this.theme.bold(this.theme.fg(y,`DOOM`))} ${this.theme.inverse(this.theme.bold(this.theme.fg(S,` PI `)))}`}compactModel(e){return e.split(`/`).at(-1)?.replace(f,``)??e}compactPath(e){let t=process.env.HOME;return t&&e.startsWith(t)?`~${e.slice(t.length)}`:e}};export{E as DoomHeader};
|
|
2
|
+
//# sourceMappingURL=doomHeader.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomHeader.mjs","names":[],"sources":["../../src/tui/doomHeader.ts"],"sourcesContent":["import path from 'node:path';\nimport { type ExtensionContext, type Theme, type ThemeColor } from '@earendil-works/pi-coding-agent';\nimport { type Component, visibleWidth } from '@earendil-works/pi-tui';\nimport { type DoomHarnessMetadata, readDoomHarnessMetadata } from '../types/harnessMetadata.ts';\nimport { alignLine, fitLine, padLine } from './rendering.ts';\n\nconst WIDE_HEADER_WIDTH = 90;\nconst FULL_STARTUP_MIN_ROWS = 24;\n// Rows Pi renders below the header: the four-line editor frame, the modeline,\n// and the spacing around them. Under-reserving scrolls the dashboard off the top.\nconst RESERVED_INTERFACE_ROWS = 8;\nconst MIN_EMPTY_STATE_HEIGHT = 3;\nconst IDENTITY_COLUMN_WIDTH = 30;\nconst COLUMN_GAP = 3;\nconst MIN_RENDER_WIDTH = 4;\nconst SECTION_SEPARATOR_ROWS = 2;\n/** Breathing room between the shell prompt Pi launched from and the dashboard. */\nconst TOP_PADDING_ROWS = 2;\nconst CONTEXT_LABEL_WIDTH = 12;\nconst SESSION_ID_PREVIEW_LENGTH = 6;\nconst EMPTY_STATE_RULE_LENGTH = 8;\nconst DEFAULT_TERMINAL_ROWS = 24;\nconst DEFAULT_METADATA_LABEL = 'default';\nconst DEFAULT_MODEL_LABEL = 'no-model';\nconst DEFAULT_THINKING_LABEL = 'off';\nconst DEFAULT_LAYERS_LABEL = 'core';\nconst DEFAULT_SESSION_LABEL = 'new';\nconst BOOT_LABEL = 'BOOT / 01';\nconst READY_STATUS = '● READY';\nconst ACTIVE_STATUS = '● ACTIVE';\nconst EMPTY_STATE_LABEL = 'NO TRANSCRIPT YET';\nconst EMPTY_STATE_GUIDANCE = 'Describe the work below. Pi keeps your existing config and session history.';\nconst PRODUCT_DESCRIPTOR = 'Pi, configured for focus.';\nconst SEGMENT_SEPARATOR = ' · ';\nconst DISPLAY_SEPARATOR = ' / ';\nconst LIST_SEPARATOR = ', ';\nconst MESSAGE_ENTRY_TYPE = 'message';\nconst TIME_LOCALE = 'en-GB';\nconst HOME_ENV = 'HOME';\nconst CLAUDE_MODEL_PREFIX = /^claude-/;\nconst REPOSITORY_LABEL = 'REPOSITORY';\nconst PROFILE_LABEL = 'PROFILE';\nconst DOMAINS_LABEL = 'DOMAINS';\nconst LAYERS_LABEL = 'LAYERS';\nconst MODE_LABEL = 'MODE';\nconst MODEL_LABEL = 'MODEL';\nconst HORIZONTAL_BORDER = '─';\nconst IDENTITY_ACCENT_BAR = '▌';\nconst READY_RULE = '━━━━━━━━';\nconst EMPTY_STATE_RULE = HORIZONTAL_BORDER.repeat(EMPTY_STATE_RULE_LENGTH);\nconst DIM_COLOR: ThemeColor = 'dim';\nconst ACCENT_COLOR: ThemeColor = 'accent';\nconst SUCCESS_COLOR: ThemeColor = 'success';\nconst MUTED_COLOR: ThemeColor = 'muted';\nconst TEXT_COLOR: ThemeColor = 'text';\nconst CODE_COLOR: ThemeColor = 'mdCode';\nconst WARNING_COLOR: ThemeColor = 'warning';\nconst HEADING_COLOR: ThemeColor = 'mdHeading';\nconst SELECTED_BG_COLOR = 'selectedBg';\nconst MUTED_BORDER_COLOR: ThemeColor = 'borderMuted';\n\ntype MetadataReader = () => DoomHarnessMetadata;\ntype TerminalRowsReader = () => number;\ntype HeaderRuntime = Pick<ExtensionContext, 'isIdle' | 'model' | 'sessionManager' | 'thinkingLevel'>;\n\nexport class DoomHeader implements Component {\n constructor(\n private readonly theme: Theme,\n private readonly cwd: string,\n private readonly readMetadata: MetadataReader = readDoomHarnessMetadata,\n private readonly runtime?: HeaderRuntime,\n private readonly getTerminalRows: TerminalRowsReader = () => DEFAULT_TERMINAL_ROWS,\n ) {}\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0) return [];\n if (width < MIN_RENDER_WIDTH) return [this.hairline(width)];\n\n const metadata = this.readMetadata();\n const hasTranscript =\n this.runtime?.sessionManager.getEntries().some((entry) => entry.type === MESSAGE_ENTRY_TYPE) ?? false;\n if (hasTranscript) return [];\n\n const terminalRows = this.getTerminalRows();\n if (terminalRows < FULL_STARTUP_MIN_ROWS) return this.renderCompact(width, metadata);\n\n const dashboard =\n width < WIDE_HEADER_WIDTH\n ? this.renderNarrowDashboard(width, metadata)\n : this.renderWideDashboard(width, metadata);\n const hints = this.renderHints(width);\n const fixedHeaderRows = TOP_PADDING_ROWS + dashboard.length + hints.length + SECTION_SEPARATOR_ROWS;\n const emptyStateHeight = Math.max(MIN_EMPTY_STATE_HEIGHT, terminalRows - RESERVED_INTERFACE_ROWS - fixedHeaderRows);\n const topPadding = Array.from({ length: TOP_PADDING_ROWS }, () => '');\n\n return [...topPadding, ...dashboard, '', ...hints, '', ...this.renderEmptyState(width, emptyStateHeight)];\n }\n\n private renderWideDashboard(width: number, metadata: DoomHarnessMetadata): string[] {\n const contextWidth = Math.max(1, width - IDENTITY_COLUMN_WIDTH - COLUMN_GAP);\n const contextHeading = alignLine(\n this.theme.bold(this.theme.fg(HEADING_COLOR, 'SESSION CONTEXT')),\n this.theme.fg(\n DIM_COLOR,\n `${new Date().toLocaleTimeString(TIME_LOCALE, { hour12: false })}${DISPLAY_SEPARATOR}local`,\n ),\n contextWidth,\n );\n const repository = this.compactPath(metadata.root ?? this.cwd);\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(LIST_SEPARATOR) || DEFAULT_METADATA_LABEL;\n const layers = metadata.layers.join(SEGMENT_SEPARATOR) || DEFAULT_LAYERS_LABEL;\n const model = this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL;\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const ready = `${this.theme.fg(SUCCESS_COLOR, READY_RULE)} ${this.theme.bold(\n this.theme.fg(SUCCESS_COLOR, 'READY'),\n )}`;\n const rows = [\n this.columns(this.identityCell(this.theme.fg(DIM_COLOR, BOOT_LABEL)), contextHeading, width),\n this.columns(\n this.identityCell(this.wordmark()),\n this.contextRow(REPOSITORY_LABEL, repository, TEXT_COLOR),\n width,\n ),\n this.columns(\n this.identityCell(this.theme.fg(MUTED_COLOR, PRODUCT_DESCRIPTOR)),\n this.contextRow(PROFILE_LABEL, profile, CODE_COLOR),\n width,\n ),\n this.columns(this.identityCell(ready), this.contextRow(DOMAINS_LABEL, domains, ACCENT_COLOR), width),\n this.columns(this.identityCell(''), this.contextRow(LAYERS_LABEL, layers, MUTED_COLOR), width),\n this.columns(this.identityCell(''), this.contextRow(MODE_LABEL, metadata.majorMode, SUCCESS_COLOR), width),\n this.columns(\n this.identityCell(''),\n this.contextRow(MODEL_LABEL, `${model}${DISPLAY_SEPARATOR}${thinking}`, WARNING_COLOR),\n width,\n ),\n ];\n\n return [...rows, this.hairline(width)];\n }\n\n private renderNarrowDashboard(width: number, metadata: DoomHarnessMetadata): string[] {\n const repository = path.basename(metadata.root ?? this.cwd) || this.cwd;\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(LIST_SEPARATOR) || DEFAULT_METADATA_LABEL;\n const session =\n this.runtime?.sessionManager.getSessionId().slice(0, SESSION_ID_PREVIEW_LENGTH) ?? DEFAULT_SESSION_LABEL;\n const layers = metadata.layers.join(SEGMENT_SEPARATOR) || DEFAULT_LAYERS_LABEL;\n const model = this.compactModel(this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL);\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const ready = this.theme.bold(this.theme.fg(SUCCESS_COLOR, READY_STATUS));\n const rows = [\n alignLine(this.identityCell(`${this.theme.fg(DIM_COLOR, BOOT_LABEL)} ${this.wordmark()}`), ready, width),\n this.identityCell(this.contextRow(REPOSITORY_LABEL, repository, TEXT_COLOR)),\n this.identityCell(\n this.contextRow(\n PROFILE_LABEL,\n `${profile}${DISPLAY_SEPARATOR}${domains}${DISPLAY_SEPARATOR}${session}`,\n CODE_COLOR,\n ),\n ),\n this.identityCell(this.contextRow(LAYERS_LABEL, layers, MUTED_COLOR)),\n this.identityCell(this.contextRow(MODE_LABEL, metadata.majorMode, SUCCESS_COLOR)),\n this.identityCell(this.contextRow(MODEL_LABEL, `${model}${DISPLAY_SEPARATOR}${thinking}`, WARNING_COLOR)),\n ].map((row) => fitLine(row, width));\n\n return [...rows, this.hairline(width)];\n }\n\n private renderCompact(width: number, metadata: DoomHarnessMetadata): string[] {\n const repository = path.basename(metadata.root ?? this.cwd) || this.cwd;\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(',') || DEFAULT_METADATA_LABEL;\n const model = this.compactModel(this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL);\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const activity = this.runtime?.isIdle() === false ? ACTIVE_STATUS : READY_STATUS;\n const rows = [\n alignLine(\n `${this.wordmark()} ${this.theme.bold(this.theme.fg(TEXT_COLOR, repository))}`,\n this.theme.fg(SUCCESS_COLOR, activity),\n width,\n ),\n fitLine(`${profile}${DISPLAY_SEPARATOR}${domains}${DISPLAY_SEPARATOR}${metadata.majorMode}`, width),\n alignLine(\n this.theme.fg(WARNING_COLOR, `${model}${DISPLAY_SEPARATOR}${thinking}`),\n this.theme.fg(DIM_COLOR, 'CTRL+SPACE leader · SPC when empty'),\n width,\n ),\n ];\n\n return [...rows, this.hairline(width)];\n }\n\n private renderHints(width: number): string[] {\n const content = [\n this.keyHint('CTRL+SPACE', 'leader anywhere'),\n this.keyHint('SPC', 'when empty'),\n this.keyHint('/', 'commands'),\n this.keyHint('!', 'shell'),\n ].join(' ');\n return [fitLine(content, width)];\n }\n\n private renderEmptyState(width: number, height: number): string[] {\n const lines = Array.from({ length: Math.max(MIN_EMPTY_STATE_HEIGHT, height) }, () => '');\n const rule = this.theme.fg(MUTED_BORDER_COLOR, EMPTY_STATE_RULE);\n const label = `${rule} ${this.theme.fg(DIM_COLOR, EMPTY_STATE_LABEL)} ${rule}`;\n const guidance = this.theme.fg(MUTED_COLOR, EMPTY_STATE_GUIDANCE);\n const labelIndex = Math.max(0, Math.floor(lines.length / SECTION_SEPARATOR_ROWS) - 1);\n lines[labelIndex] = this.centerLine(label, width);\n if (lines.length > labelIndex + 1) lines[labelIndex + 1] = this.centerLine(guidance, width);\n\n return lines;\n }\n\n private hairline(width: number): string {\n return this.theme.fg(MUTED_BORDER_COLOR, HORIZONTAL_BORDER.repeat(Math.max(0, width)));\n }\n\n private centerLine(content: string, width: number): string {\n const fitted = fitLine(content, width);\n const leftPadding = ' '.repeat(Math.max(0, Math.floor((width - visibleWidth(fitted)) / SECTION_SEPARATOR_ROWS)));\n return fitLine(`${leftPadding}${fitted}`, width);\n }\n\n private identityCell(content: string): string {\n const bar = this.theme.fg(ACCENT_COLOR, IDENTITY_ACCENT_BAR);\n return content ? `${bar} ${content}` : bar;\n }\n\n private columns(left: string, right: string, width: number): string {\n const rightWidth = Math.max(0, width - IDENTITY_COLUMN_WIDTH - COLUMN_GAP);\n return fitLine(\n `${padLine(left, IDENTITY_COLUMN_WIDTH)}${' '.repeat(COLUMN_GAP)}${fitLine(right, rightWidth)}`,\n width,\n );\n }\n\n private contextRow(label: string, value: string, color: ThemeColor): string {\n return `${this.theme.fg(DIM_COLOR, label.padEnd(CONTEXT_LABEL_WIDTH))}${this.theme.fg(color, value)}`;\n }\n\n private keyHint(key: string, label: string): string {\n const badge = this.theme.bg(SELECTED_BG_COLOR, this.theme.fg(ACCENT_COLOR, ` ${key} `));\n return `${badge} ${this.theme.fg(DIM_COLOR, label)}`;\n }\n\n private wordmark(): string {\n return `${this.theme.bold(this.theme.fg(ACCENT_COLOR, 'DOOM'))} ${this.theme.inverse(\n this.theme.bold(this.theme.fg(TEXT_COLOR, ' PI ')),\n )}`;\n }\n\n private compactModel(model: string): string {\n return model.split('/').at(-1)?.replace(CLAUDE_MODEL_PREFIX, '') ?? model;\n }\n\n private compactPath(value: string): string {\n const home = process.env[HOME_ENV];\n return home && value.startsWith(home) ? `~${value.slice(home.length)}` : value;\n }\n}\n"],"mappings":"4NAMA,MAgBM,EAAyB,UACzB,EAAsB,WAEtB,EAAuB,OAEvB,EAAa,YACb,EAAe,UAKf,EAAoB,MAMpB,EAAsB,WACtB,EAAmB,aACnB,EAAgB,UAEhB,EAAe,SACf,EAAa,OACb,EAAc,QAId,EAAmB,IAAkB,OAAO,CAAuB,EAEnE,EAA2B,SAC3B,EAA4B,UAC5B,EAA0B,QAC1B,EAAyB,OACzB,EAAyB,SACzB,EAA4B,UAG5B,EAAiC,cAMvC,IAAa,EAAb,KAA6C,CAExB,MACA,IACA,aACA,QACA,gBALnB,YACE,EACA,EACA,EAAgD,EAChD,EACA,MAA6D,GAC7D,CALiB,KAAA,MAAA,EACA,KAAA,IAAA,EACA,KAAA,aAAA,EACA,KAAA,QAAA,EACA,KAAA,gBAAA,CAChB,CAEH,YAAmB,CAAC,CAEpB,OAAO,EAAyB,CAC9B,GAAI,GAAS,EAAG,MAAO,CAAC,EACxB,GAAI,EAAQ,EAAkB,MAAO,CAAC,KAAK,SAAS,CAAK,CAAC,EAE1D,IAAM,EAAW,KAAK,aAAa,EAGnC,GADE,KAAK,SAAS,eAAe,WAAW,CAAC,CAAC,KAAM,GAAU,EAAM,OAAS,SAAkB,GAAK,GAC/E,MAAO,CAAC,EAE3B,IAAM,EAAe,KAAK,gBAAgB,EAC1C,GAAI,EAAe,GAAuB,OAAO,KAAK,cAAc,EAAO,CAAQ,EAEnF,IAAM,EACJ,EAAQ,GACJ,KAAK,sBAAsB,EAAO,CAAQ,EAC1C,KAAK,oBAAoB,EAAO,CAAQ,EACxC,EAAQ,KAAK,YAAY,CAAK,EAC9B,EAAkB,EAAmB,EAAU,OAAS,EAAM,OAAS,EACvE,EAAmB,KAAK,IAAI,EAAwB,EAAe,EAA0B,CAAe,EAGlH,MAAO,CAAC,GAFW,MAAM,KAAK,CAAE,OAAQ,CAAiB,MAAS,EAE9C,EAAG,GAAG,EAAW,GAAI,GAAG,EAAO,GAAI,GAAG,KAAK,iBAAiB,EAAO,CAAgB,CAAC,CAC1G,CAEA,oBAA4B,EAAe,EAAyC,CAClF,IAAM,EAAe,KAAK,IAAI,EAAG,EAAQ,GAAwB,CAAU,EACrE,EAAiB,EACrB,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,YAAe,iBAAiB,CAAC,EAC/D,KAAK,MAAM,GACT,MACA,GAAG,IAAI,KAAK,CAAA,CAAE,mBAAmB,QAAa,CAAE,OAAQ,EAAM,CAAC,WACjE,EACA,CACF,EACM,EAAa,KAAK,YAAY,EAAS,MAAQ,KAAK,GAAG,EACvD,EAAU,EAAS,SAAW,EAC9B,EAAU,EAAS,QAAQ,KAAK,IAAc,GAAK,EACnD,EAAS,EAAS,OAAO,KAAK,CAAiB,GAAK,EACpD,EAAQ,KAAK,SAAS,OAAO,IAAM,EACnC,EAAW,KAAK,SAAS,eAAiB,MAC1C,EAAQ,GAAG,KAAK,MAAM,GAAG,EAAe,UAAU,EAAE,GAAG,KAAK,MAAM,KACtE,KAAK,MAAM,GAAG,EAAe,OAAO,CACtC,IAuBA,MAAO,CArBL,KAAK,QAAQ,KAAK,aAAa,KAAK,MAAM,GAAG,MAAW,CAAU,CAAC,EAAG,EAAgB,CAAK,EAC3F,KAAK,QACH,KAAK,aAAa,KAAK,SAAS,CAAC,EACjC,KAAK,WAAW,EAAkB,EAAY,CAAU,EACxD,CACF,EACA,KAAK,QACH,KAAK,aAAa,KAAK,MAAM,GAAG,EAAa,2BAAkB,CAAC,EAChE,KAAK,WAAW,EAAe,EAAS,CAAU,EAClD,CACF,EACA,KAAK,QAAQ,KAAK,aAAa,CAAK,EAAG,KAAK,WAAW,UAAe,EAAS,CAAY,EAAG,CAAK,EACnG,KAAK,QAAQ,KAAK,aAAa,EAAE,EAAG,KAAK,WAAW,EAAc,EAAQ,CAAW,EAAG,CAAK,EAC7F,KAAK,QAAQ,KAAK,aAAa,EAAE,EAAG,KAAK,WAAW,EAAY,EAAS,UAAW,CAAa,EAAG,CAAK,EACzG,KAAK,QACH,KAAK,aAAa,EAAE,EACpB,KAAK,WAAW,EAAa,GAAG,OAA4B,IAAY,CAAa,EACrF,CACF,EAGe,KAAK,SAAS,CAAK,CAAC,CACvC,CAEA,sBAA8B,EAAe,EAAyC,CACpF,IAAM,EAAa,EAAK,SAAS,EAAS,MAAQ,KAAK,GAAG,GAAK,KAAK,IAC9D,EAAU,EAAS,SAAW,EAC9B,EAAU,EAAS,QAAQ,KAAK,IAAc,GAAK,EACnD,EACJ,KAAK,SAAS,eAAe,aAAa,CAAC,CAAC,MAAM,EAAG,CAAyB,GAAK,MAC/E,EAAS,EAAS,OAAO,KAAK,CAAiB,GAAK,EACpD,EAAQ,KAAK,aAAa,KAAK,SAAS,OAAO,IAAM,CAAmB,EACxE,EAAW,KAAK,SAAS,eAAiB,MAC1C,EAAQ,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,EAAe,CAAY,CAAC,EAgBxE,MAAO,CAAC,GAfK,CACX,EAAU,KAAK,aAAa,GAAG,KAAK,MAAM,GAAG,MAAW,CAAU,EAAE,IAAI,KAAK,SAAS,GAAG,EAAG,EAAO,CAAK,EACxG,KAAK,aAAa,KAAK,WAAW,EAAkB,EAAY,CAAU,CAAC,EAC3E,KAAK,aACH,KAAK,WACH,EACA,GAAG,OAA8B,OAA8B,IAC/D,CACF,CACF,EACA,KAAK,aAAa,KAAK,WAAW,EAAc,EAAQ,CAAW,CAAC,EACpE,KAAK,aAAa,KAAK,WAAW,EAAY,EAAS,UAAW,CAAa,CAAC,EAChF,KAAK,aAAa,KAAK,WAAW,EAAa,GAAG,OAA4B,IAAY,CAAa,CAAC,CAC1G,CAAC,CAAC,IAAK,GAAQ,EAAQ,EAAK,CAAK,CAEnB,EAAG,KAAK,SAAS,CAAK,CAAC,CACvC,CAEA,cAAsB,EAAe,EAAyC,CAC5E,IAAM,EAAa,EAAK,SAAS,EAAS,MAAQ,KAAK,GAAG,GAAK,KAAK,IAC9D,EAAU,EAAS,SAAW,EAC9B,EAAU,EAAS,QAAQ,KAAK,GAAG,GAAK,EACxC,EAAQ,KAAK,aAAa,KAAK,SAAS,OAAO,IAAM,CAAmB,EACxE,EAAW,KAAK,SAAS,eAAiB,MAC1C,EAAW,KAAK,SAAS,OAAO,IAAM,GAAQ,WAAgB,EAepE,MAAO,CAbL,EACE,GAAG,KAAK,SAAS,EAAE,IAAI,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,EAAY,CAAU,CAAC,IAC5E,KAAK,MAAM,GAAG,EAAe,CAAQ,EACrC,CACF,EACA,EAAQ,GAAG,OAA8B,OAA8B,EAAS,YAAa,CAAK,EAClG,EACE,KAAK,MAAM,GAAG,EAAe,GAAG,OAA4B,GAAU,EACtE,KAAK,MAAM,GAAG,MAAW,oCAAoC,EAC7D,CACF,EAGe,KAAK,SAAS,CAAK,CAAC,CACvC,CAEA,YAAoB,EAAyB,CAC3C,IAAM,EAAU,CACd,KAAK,QAAQ,aAAc,iBAAiB,EAC5C,KAAK,QAAQ,MAAO,YAAY,EAChC,KAAK,QAAQ,IAAK,UAAU,EAC5B,KAAK,QAAQ,IAAK,OAAO,CAC3B,CAAC,CAAC,KAAK,KAAK,EACZ,MAAO,CAAC,EAAQ,EAAS,CAAK,CAAC,CACjC,CAEA,iBAAyB,EAAe,EAA0B,CAChE,IAAM,EAAQ,MAAM,KAAK,CAAE,OAAQ,KAAK,IAAI,EAAwB,CAAM,CAAE,MAAS,EAAE,EACjF,EAAO,KAAK,MAAM,GAAG,EAAoB,CAAgB,EACzD,EAAQ,GAAG,EAAK,GAAG,KAAK,MAAM,GAAG,MAAW,mBAAiB,EAAE,GAAG,IAClE,EAAW,KAAK,MAAM,GAAG,EAAa,6EAAoB,EAC1D,EAAa,KAAK,IAAI,EAAG,KAAK,MAAM,EAAM,OAAS,CAAsB,EAAI,CAAC,EAIpF,MAHA,GAAM,GAAc,KAAK,WAAW,EAAO,CAAK,EAC5C,EAAM,OAAS,EAAa,IAAG,EAAM,EAAa,GAAK,KAAK,WAAW,EAAU,CAAK,GAEnF,CACT,CAEA,SAAiB,EAAuB,CACtC,OAAO,KAAK,MAAM,GAAG,EAAoB,IAAkB,OAAO,KAAK,IAAI,EAAG,CAAK,CAAC,CAAC,CACvF,CAEA,WAAmB,EAAiB,EAAuB,CACzD,IAAM,EAAS,EAAQ,EAAS,CAAK,EAC/B,EAAc,IAAI,OAAO,KAAK,IAAI,EAAG,KAAK,OAAO,EAAQ,EAAa,CAAM,GAAK,CAAsB,CAAC,CAAC,EAC/G,OAAO,EAAQ,GAAG,IAAc,IAAU,CAAK,CACjD,CAEA,aAAqB,EAAyB,CAC5C,IAAM,EAAM,KAAK,MAAM,GAAG,EAAc,GAAmB,EAC3D,OAAO,EAAU,GAAG,EAAI,GAAG,IAAY,CACzC,CAEA,QAAgB,EAAc,EAAe,EAAuB,CAClE,IAAM,EAAa,KAAK,IAAI,EAAG,EAAQ,GAAwB,CAAU,EACzE,OAAO,EACL,GAAG,EAAQ,EAAM,EAAqB,IAAI,IAAI,OAAO,CAAU,IAAI,EAAQ,EAAO,CAAU,IAC5F,CACF,CACF,CAEA,WAAmB,EAAe,EAAe,EAA2B,CAC1E,MAAO,GAAG,KAAK,MAAM,GAAG,MAAW,EAAM,OAAO,EAAmB,CAAC,IAAI,KAAK,MAAM,GAAG,EAAO,CAAK,GACpG,CAEA,QAAgB,EAAa,EAAuB,CAElD,MAAO,GADO,KAAK,MAAM,GAAG,aAAmB,KAAK,MAAM,GAAG,EAAc,IAAI,EAAI,EAAE,CACvE,EAAE,GAAG,KAAK,MAAM,GAAG,MAAW,CAAK,GACnD,CAEA,UAA2B,CACzB,MAAO,GAAG,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,EAAc,MAAM,CAAC,EAAE,GAAG,KAAK,MAAM,QAC3E,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,EAAY,MAAM,CAAC,CACnD,GACF,CAEA,aAAqB,EAAuB,CAC1C,OAAO,EAAM,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAqB,EAAE,GAAK,CACtE,CAEA,YAAoB,EAAuB,CACzC,IAAM,EAAO,QAAQ,IAAI,KACzB,OAAO,GAAQ,EAAM,WAAW,CAAI,EAAI,IAAI,EAAM,MAAM,EAAK,MAAM,IAAM,CAC3E,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./rendering.cjs");let t=require("@earendil-works/pi-tui");const n=`borderMuted`,r=`accent`,i=[`╭`,`╮`],a=[`├`,`┤`],o=[`╰`,`╯`],s={list:`↑↓`,detail:`JK`},c={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}};function l(e,n){return!e||n<=0||n<Math.min((0,t.visibleWidth)(e),8)?``:(0,t.truncateToWidth)(e,n,`…`)}var u=class{tui;theme;constructor(e,t){this.tui=e,this.theme=t}invalidate(){this.tui.requestRender()}render(t){let r=Math.max(0,Math.floor(t)),s=Math.max(0,Math.floor(this.tui.terminal?.rows??24));if(r===0||s===0)return[];let c=this.getChrome();if(s<6||r<8)return this.renderCompact(c,r,s);let l=r-2,u=l-2,d=s-6,f=this.renderBody(u,d).slice(0,d);for(;f.length<d;)f.push(``);let p=c.accent??n;return[this.borderRow(i,l,p),this.contentRow(this.headerRow(c,u),u,r,p),this.borderRow(a,l,p,n),...f.map(e=>this.contentRow(e,u,r,p)),this.borderRow(a,l,p,n),this.contentRow(this.footerRow(c,u),u,r,p),this.borderRow(o,l,p)].map(t=>e.padLine(t,r))}borderRow([e,t],r,i=n,a=i){let o=i??n;return[this.theme.fg(o,e),this.theme.fg(a??o,`─`.repeat(r)),this.theme.fg(o,t)].join(``)}contentRow(t,r,i,a=n){let o=this.theme.fg(a??n,`│`);return e.frameLine(` ${e.padLine(t,r)} `,i,o,o)}headerRow(e,n){let i=e.accent,a=i?n-2:n,o=(0,t.truncateToWidth)(e.title,Math.max(0,a),`…`),s=i?this.theme.inverse(this.theme.bold(this.theme.fg(i,` ${o} `))):this.theme.bold(this.theme.fg(r,o)),c=(0,t.visibleWidth)(o)+(i?2:0),u=l(e.breadcrumb,n-c-2);return u&&(s+=`${` `.repeat(2)}${this.theme.fg(`dim`,u)}`,c+=2+(0,t.visibleWidth)(u)),this.withRightStatus(s,c,e.headerRight,n)}footerRow(e,n){let r=e.footerHints?.length?this.hintLegend(e.footerHints,n):this.theme.fg(`dim`,(0,t.truncateToWidth)(e.footer,n,`…`));return this.withRightStatus(r,(0,t.visibleWidth)(r),e.footerRight,n)}hintLegend(e,n){let r=``,i=0;for(let[a,o]of e){let e=(0,t.visibleWidth)(a)+(0,t.visibleWidth)(o)+6;if(i+e>n)break;let s=this.theme.bg(`selectedBg`,this.theme.fg(`text`,` ${a} `));r+=`${r?` `:``}${s}${this.theme.fg(`dim`,` ${o}`)}`,i+=e}return r}withRightStatus(e,n,r,i){let a=r?(0,t.visibleWidth)(r):0;return!r||a===0||n+2+a>i?e:`${e}${` `.repeat(i-n-a)}${this.theme.fg(`dim`,r)}`}renderCompact(t,n,i){let a=Array.from({length:i},()=>``);return a[0]=this.theme.bold(this.theme.fg(r,t.title)),i>1&&(a[i-1]=this.theme.fg(`dim`,t.footer)),a.map(t=>e.padLine(e.fitLine(t,n),n))}};exports.DOOM_FULLSCREEN_UI_OPTIONS=c,exports.DOOM_NAVIGATION_KEYS=s,exports.DOOM_OVERLAY_ACCENT=`mdHeading`,exports.DoomOverlay=u;
|
|
2
|
+
//# sourceMappingURL=doomOverlay.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomOverlay.cjs","names":["visibleWidth","truncateToWidth","padLine","frameLine","fitLine"],"sources":["../../src/tui/doomOverlay.ts"],"sourcesContent":["import type { Theme } from '@earendil-works/pi-coding-agent';\nimport { type Component, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { fitLine, frameLine, padLine } from './rendering.ts';\n\nconst DEFAULT_TERMINAL_ROWS = 24;\nconst FULL_CHROME_ROWS = 6;\n/** Two frame columns and two gutter columns, leaving content worth framing. */\nconst MIN_FRAMED_WIDTH = 8;\nconst GUTTER = ' ';\nconst GUTTER_COLUMNS = 2;\n/** Blank columns kept between a row's left cluster and its right-aligned status. */\nconst ROW_GAP = 2;\n/** A secondary segment thinner than this is dropped rather than shredded into a stub. */\nconst MIN_SEGMENT_WIDTH = 8;\nconst ELLIPSIS = '…';\nconst BORDER_COLOR = 'borderMuted';\nconst TEXT_COLOR = 'text';\n/** Filled key caps read as pressable, matching the doom mockups' footers. */\nconst HINT_CAP_BACKGROUND = 'selectedBg';\nconst HINT_SEPARATOR = ' ';\n/** A cap's two pad columns plus the space before its label. */\nconst HINT_CHROME_COLUMNS = 6;\nconst TITLE_COLOR = 'accent';\nconst SECONDARY_COLOR = 'dim';\nconst HORIZONTAL_BORDER = '─';\nconst VERTICAL_BORDER = '│';\nconst TOP_CORNERS = ['╭', '╮'] as const;\nconst DIVIDER_JOINTS = ['├', '┤'] as const;\nconst BOTTOM_CORNERS = ['╰', '╯'] as const;\n\nexport interface DoomOverlayTui {\n terminal?: {\n rows: number;\n columns?: number;\n };\n requestRender(force?: boolean): void;\n}\n\nexport interface DoomOverlayChrome {\n title: string;\n breadcrumb?: string;\n headerRight?: string;\n footer: string;\n footerRight?: string;\n /**\n * Key/label pairs rendered as filled caps, e.g. `[ enter ] edit subject`.\n * Takes precedence over `footer`, which stays for surfaces that want a plain\n * sentence rather than a key legend.\n */\n footerHints?: readonly (readonly [string, string])[];\n /**\n * Signature colour for the surface. When set, the frame takes it and the\n * title renders as a filled badge rather than plain accent text, matching the\n * doom mockups. Left unset, the frame stays muted and the title plain.\n */\n accent?: Parameters<Theme['fg']>[0];\n}\n\n/**\n * One signature colour for every doom overlay: the frame and the title badge.\n * Per-surface colours were tried and read as arbitrary, and they also borrowed\n * tokens that mean something else (`warning`, `syntaxNumber`), so a theme edit\n * elsewhere would have repainted a frame. Change it here to change them all.\n */\nexport const DOOM_OVERLAY_ACCENT = 'mdHeading' satisfies DoomOverlayChrome['accent'];\n\n/** Canonical legend tokens for list/roster navigation and detail-pane scrolling. */\nexport const DOOM_NAVIGATION_KEYS = {\n list: '↑↓',\n detail: 'JK',\n} as const;\n\nexport const DOOM_FULLSCREEN_UI_OPTIONS = {\n overlay: true,\n overlayOptions: {\n anchor: 'top-left',\n width: '100%',\n maxHeight: '100%',\n margin: 0,\n },\n} as const;\n\n/**\n * Fits a secondary segment into `budget`, or drops it entirely when too few\n * columns remain for it to stay readable. Truncating everything to fit is what\n * makes a tight header read as one run-on string.\n */\nfunction fitSegment(text: string | undefined, budget: number): string {\n if (!text || budget <= 0) return '';\n return budget < Math.min(visibleWidth(text), MIN_SEGMENT_WIDTH) ? '' : truncateToWidth(text, budget, ELLIPSIS);\n}\n\nexport abstract class DoomOverlay implements Component {\n protected constructor(\n protected readonly tui: DoomOverlayTui,\n protected readonly theme: Theme,\n ) {}\n\n invalidate(): void {\n this.tui.requestRender();\n }\n\n render(width: number): string[] {\n const safeWidth = Math.max(0, Math.floor(width));\n const height = Math.max(0, Math.floor(this.tui.terminal?.rows ?? DEFAULT_TERMINAL_ROWS));\n if (safeWidth === 0 || height === 0) return [];\n\n const chrome = this.getChrome();\n if (height < FULL_CHROME_ROWS || safeWidth < MIN_FRAMED_WIDTH) {\n return this.renderCompact(chrome, safeWidth, height);\n }\n\n const innerWidth = safeWidth - GUTTER_COLUMNS;\n const contentWidth = innerWidth - GUTTER_COLUMNS;\n const bodyHeight = height - FULL_CHROME_ROWS;\n const body = this.renderBody(contentWidth, bodyHeight).slice(0, bodyHeight);\n while (body.length < bodyHeight) body.push('');\n\n const frame = chrome.accent ?? BORDER_COLOR;\n return [\n this.borderRow(TOP_CORNERS, innerWidth, frame),\n this.contentRow(this.headerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n ...body.map((line) => this.contentRow(line, contentWidth, safeWidth, frame)),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n this.contentRow(this.footerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(BOTTOM_CORNERS, innerWidth, frame),\n ].map((line) => padLine(line, safeWidth));\n }\n\n protected abstract getChrome(): DoomOverlayChrome;\n\n protected abstract renderBody(width: number, height: number): string[];\n\n /**\n * Edge glyphs and the rule between them are coloured separately: the outer\n * rectangle carries the signature colour while an internal separator stays\n * muted, meeting the frame at accent-coloured junctions.\n */\n private borderRow(\n [left, right]: readonly [string, string],\n innerWidth: number,\n edge: DoomOverlayChrome['accent'] = BORDER_COLOR,\n fill: DoomOverlayChrome['accent'] = edge,\n ): string {\n const edgeColour = edge ?? BORDER_COLOR;\n return [\n this.theme.fg(edgeColour, left),\n this.theme.fg(fill ?? edgeColour, HORIZONTAL_BORDER.repeat(innerWidth)),\n this.theme.fg(edgeColour, right),\n ].join('');\n }\n\n private contentRow(\n content: string,\n contentWidth: number,\n width: number,\n colour: DoomOverlayChrome['accent'] = BORDER_COLOR,\n ): string {\n const edge = this.theme.fg(colour ?? BORDER_COLOR, VERTICAL_BORDER);\n return frameLine(`${GUTTER}${padLine(content, contentWidth)}${GUTTER}`, width, edge, edge);\n }\n\n private headerRow(chrome: DoomOverlayChrome, width: number): string {\n // A signature colour turns the title into a filled badge, as the mockups\n // show; without one it stays plain accent text.\n const accent = chrome.accent;\n const titleBudget = accent ? width - GUTTER_COLUMNS : width;\n const title = truncateToWidth(chrome.title, Math.max(0, titleBudget), ELLIPSIS);\n let left = accent\n ? this.theme.inverse(this.theme.bold(this.theme.fg(accent, ` ${title} `)))\n : this.theme.bold(this.theme.fg(TITLE_COLOR, title));\n let leftWidth = visibleWidth(title) + (accent ? GUTTER_COLUMNS : 0);\n\n // The breadcrumb claims room before the right status: it says where you are,\n // which outranks ambient context when the terminal is narrow.\n const breadcrumb = fitSegment(chrome.breadcrumb, width - leftWidth - ROW_GAP);\n if (breadcrumb) {\n left += `${' '.repeat(ROW_GAP)}${this.theme.fg(SECONDARY_COLOR, breadcrumb)}`;\n leftWidth += ROW_GAP + visibleWidth(breadcrumb);\n }\n return this.withRightStatus(left, leftWidth, chrome.headerRight, width);\n }\n\n private footerRow(chrome: DoomOverlayChrome, width: number): string {\n const footer = chrome.footerHints?.length\n ? this.hintLegend(chrome.footerHints, width)\n : this.theme.fg(SECONDARY_COLOR, truncateToWidth(chrome.footer, width, ELLIPSIS));\n return this.withRightStatus(footer, visibleWidth(footer), chrome.footerRight, width);\n }\n\n /**\n * Key legend with each key in a filled cap. Every segment sets its own colour\n * so the caps survive: a cap's reset would otherwise strip the surrounding\n * dim from the labels that follow it.\n */\n private hintLegend(hints: readonly (readonly [string, string])[], width: number): string {\n let legend = '';\n let used = 0;\n for (const [key, label] of hints) {\n const segmentWidth = visibleWidth(key) + visibleWidth(label) + HINT_CHROME_COLUMNS;\n if (used + segmentWidth > width) break;\n const cap = this.theme.bg(HINT_CAP_BACKGROUND, this.theme.fg(TEXT_COLOR, ` ${key} `));\n legend += `${legend ? HINT_SEPARATOR : ''}${cap}${this.theme.fg(SECONDARY_COLOR, ` ${label}`)}`;\n used += segmentWidth;\n }\n return legend;\n }\n\n /**\n * Right-aligns a status cluster, dropping it whole when the row cannot spare\n * the columns. A clipped status reads as a stub (`doom-log…`) that carries\n * less than the blank space it costs.\n */\n private withRightStatus(left: string, leftWidth: number, right: string | undefined, width: number): string {\n const statusWidth = right ? visibleWidth(right) : 0;\n if (!right || statusWidth === 0 || leftWidth + ROW_GAP + statusWidth > width) return left;\n return `${left}${' '.repeat(width - leftWidth - statusWidth)}${this.theme.fg(SECONDARY_COLOR, right)}`;\n }\n\n private renderCompact(chrome: DoomOverlayChrome, width: number, height: number): string[] {\n const lines = Array.from({ length: height }, () => '');\n lines[0] = this.theme.bold(this.theme.fg(TITLE_COLOR, chrome.title));\n if (height > 1) lines[height - 1] = this.theme.fg(SECONDARY_COLOR, chrome.footer);\n return lines.map((line) => padLine(fitLine(line, width), width));\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Component } from "@earendil-works/pi-tui";
|
|
3
|
+
//#region src/tui/doomOverlay.d.ts
|
|
4
|
+
interface DoomOverlayTui {
|
|
5
|
+
terminal?: {
|
|
6
|
+
rows: number;
|
|
7
|
+
columns?: number;
|
|
8
|
+
};
|
|
9
|
+
requestRender(force?: boolean): void;
|
|
10
|
+
}
|
|
11
|
+
interface DoomOverlayChrome {
|
|
12
|
+
title: string;
|
|
13
|
+
breadcrumb?: string;
|
|
14
|
+
headerRight?: string;
|
|
15
|
+
footer: string;
|
|
16
|
+
footerRight?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Key/label pairs rendered as filled caps, e.g. `[ enter ] edit subject`.
|
|
19
|
+
* Takes precedence over `footer`, which stays for surfaces that want a plain
|
|
20
|
+
* sentence rather than a key legend.
|
|
21
|
+
*/
|
|
22
|
+
footerHints?: readonly (readonly [string, string])[];
|
|
23
|
+
/**
|
|
24
|
+
* Signature colour for the surface. When set, the frame takes it and the
|
|
25
|
+
* title renders as a filled badge rather than plain accent text, matching the
|
|
26
|
+
* doom mockups. Left unset, the frame stays muted and the title plain.
|
|
27
|
+
*/
|
|
28
|
+
accent?: Parameters<Theme['fg']>[0];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One signature colour for every doom overlay: the frame and the title badge.
|
|
32
|
+
* Per-surface colours were tried and read as arbitrary, and they also borrowed
|
|
33
|
+
* tokens that mean something else (`warning`, `syntaxNumber`), so a theme edit
|
|
34
|
+
* elsewhere would have repainted a frame. Change it here to change them all.
|
|
35
|
+
*/
|
|
36
|
+
declare const DOOM_OVERLAY_ACCENT = "mdHeading";
|
|
37
|
+
/** Canonical legend tokens for list/roster navigation and detail-pane scrolling. */
|
|
38
|
+
declare const DOOM_NAVIGATION_KEYS: {
|
|
39
|
+
readonly list: "↑↓";
|
|
40
|
+
readonly detail: "JK";
|
|
41
|
+
};
|
|
42
|
+
declare const DOOM_FULLSCREEN_UI_OPTIONS: {
|
|
43
|
+
readonly overlay: true;
|
|
44
|
+
readonly overlayOptions: {
|
|
45
|
+
readonly anchor: "top-left";
|
|
46
|
+
readonly width: "100%";
|
|
47
|
+
readonly maxHeight: "100%";
|
|
48
|
+
readonly margin: 0;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
declare abstract class DoomOverlay implements Component {
|
|
52
|
+
protected readonly tui: DoomOverlayTui;
|
|
53
|
+
protected readonly theme: Theme;
|
|
54
|
+
protected constructor(tui: DoomOverlayTui, theme: Theme);
|
|
55
|
+
invalidate(): void;
|
|
56
|
+
render(width: number): string[];
|
|
57
|
+
protected abstract getChrome(): DoomOverlayChrome;
|
|
58
|
+
protected abstract renderBody(width: number, height: number): string[];
|
|
59
|
+
/**
|
|
60
|
+
* Edge glyphs and the rule between them are coloured separately: the outer
|
|
61
|
+
* rectangle carries the signature colour while an internal separator stays
|
|
62
|
+
* muted, meeting the frame at accent-coloured junctions.
|
|
63
|
+
*/
|
|
64
|
+
private borderRow;
|
|
65
|
+
private contentRow;
|
|
66
|
+
private headerRow;
|
|
67
|
+
private footerRow;
|
|
68
|
+
/**
|
|
69
|
+
* Key legend with each key in a filled cap. Every segment sets its own colour
|
|
70
|
+
* so the caps survive: a cap's reset would otherwise strip the surrounding
|
|
71
|
+
* dim from the labels that follow it.
|
|
72
|
+
*/
|
|
73
|
+
private hintLegend;
|
|
74
|
+
/**
|
|
75
|
+
* Right-aligns a status cluster, dropping it whole when the row cannot spare
|
|
76
|
+
* the columns. A clipped status reads as a stub (`doom-log…`) that carries
|
|
77
|
+
* less than the blank space it costs.
|
|
78
|
+
*/
|
|
79
|
+
private withRightStatus;
|
|
80
|
+
private renderCompact;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { DOOM_FULLSCREEN_UI_OPTIONS, DOOM_NAVIGATION_KEYS, DOOM_OVERLAY_ACCENT, DoomOverlay, DoomOverlayChrome, DoomOverlayTui };
|
|
84
|
+
//# sourceMappingURL=doomOverlay.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomOverlay.d.cts","names":[],"sources":["../../src/tui/doomOverlay.ts"],"mappings":";;;UA8BiB;EACf;IACE;IACA;;EAEF,cAAc;;UAGC;EACf;EACA;EACA;EACA;EACA;;;;;;EAMA;;;;;;EAMA,SAAS,WAAW;;;;;;;;cAST;;cAGA;WAAA;WAAA;;cAKA;;;;;;;;;uBAoBS,uBAAuB;qBAEtB,KAAK;qBACL,OAAO;YAFnB,YACY,KAAK,gBACL,OAAO;EAG5B;EAIA,OAAO;qBA4BY,aAAa;qBAEb,WAAW,eAAe;;;;;;UAOrC;UAcA;UAUA;UAqBA;;;;;;UAYA;;;;;;UAkBA;UAMA"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Component } from "@earendil-works/pi-tui";
|
|
2
|
+
import { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
//#region src/tui/doomOverlay.d.ts
|
|
4
|
+
interface DoomOverlayTui {
|
|
5
|
+
terminal?: {
|
|
6
|
+
rows: number;
|
|
7
|
+
columns?: number;
|
|
8
|
+
};
|
|
9
|
+
requestRender(force?: boolean): void;
|
|
10
|
+
}
|
|
11
|
+
interface DoomOverlayChrome {
|
|
12
|
+
title: string;
|
|
13
|
+
breadcrumb?: string;
|
|
14
|
+
headerRight?: string;
|
|
15
|
+
footer: string;
|
|
16
|
+
footerRight?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Key/label pairs rendered as filled caps, e.g. `[ enter ] edit subject`.
|
|
19
|
+
* Takes precedence over `footer`, which stays for surfaces that want a plain
|
|
20
|
+
* sentence rather than a key legend.
|
|
21
|
+
*/
|
|
22
|
+
footerHints?: readonly (readonly [string, string])[];
|
|
23
|
+
/**
|
|
24
|
+
* Signature colour for the surface. When set, the frame takes it and the
|
|
25
|
+
* title renders as a filled badge rather than plain accent text, matching the
|
|
26
|
+
* doom mockups. Left unset, the frame stays muted and the title plain.
|
|
27
|
+
*/
|
|
28
|
+
accent?: Parameters<Theme['fg']>[0];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One signature colour for every doom overlay: the frame and the title badge.
|
|
32
|
+
* Per-surface colours were tried and read as arbitrary, and they also borrowed
|
|
33
|
+
* tokens that mean something else (`warning`, `syntaxNumber`), so a theme edit
|
|
34
|
+
* elsewhere would have repainted a frame. Change it here to change them all.
|
|
35
|
+
*/
|
|
36
|
+
declare const DOOM_OVERLAY_ACCENT = "mdHeading";
|
|
37
|
+
/** Canonical legend tokens for list/roster navigation and detail-pane scrolling. */
|
|
38
|
+
declare const DOOM_NAVIGATION_KEYS: {
|
|
39
|
+
readonly list: "↑↓";
|
|
40
|
+
readonly detail: "JK";
|
|
41
|
+
};
|
|
42
|
+
declare const DOOM_FULLSCREEN_UI_OPTIONS: {
|
|
43
|
+
readonly overlay: true;
|
|
44
|
+
readonly overlayOptions: {
|
|
45
|
+
readonly anchor: "top-left";
|
|
46
|
+
readonly width: "100%";
|
|
47
|
+
readonly maxHeight: "100%";
|
|
48
|
+
readonly margin: 0;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
declare abstract class DoomOverlay implements Component {
|
|
52
|
+
protected readonly tui: DoomOverlayTui;
|
|
53
|
+
protected readonly theme: Theme;
|
|
54
|
+
protected constructor(tui: DoomOverlayTui, theme: Theme);
|
|
55
|
+
invalidate(): void;
|
|
56
|
+
render(width: number): string[];
|
|
57
|
+
protected abstract getChrome(): DoomOverlayChrome;
|
|
58
|
+
protected abstract renderBody(width: number, height: number): string[];
|
|
59
|
+
/**
|
|
60
|
+
* Edge glyphs and the rule between them are coloured separately: the outer
|
|
61
|
+
* rectangle carries the signature colour while an internal separator stays
|
|
62
|
+
* muted, meeting the frame at accent-coloured junctions.
|
|
63
|
+
*/
|
|
64
|
+
private borderRow;
|
|
65
|
+
private contentRow;
|
|
66
|
+
private headerRow;
|
|
67
|
+
private footerRow;
|
|
68
|
+
/**
|
|
69
|
+
* Key legend with each key in a filled cap. Every segment sets its own colour
|
|
70
|
+
* so the caps survive: a cap's reset would otherwise strip the surrounding
|
|
71
|
+
* dim from the labels that follow it.
|
|
72
|
+
*/
|
|
73
|
+
private hintLegend;
|
|
74
|
+
/**
|
|
75
|
+
* Right-aligns a status cluster, dropping it whole when the row cannot spare
|
|
76
|
+
* the columns. A clipped status reads as a stub (`doom-log…`) that carries
|
|
77
|
+
* less than the blank space it costs.
|
|
78
|
+
*/
|
|
79
|
+
private withRightStatus;
|
|
80
|
+
private renderCompact;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { DOOM_FULLSCREEN_UI_OPTIONS, DOOM_NAVIGATION_KEYS, DOOM_OVERLAY_ACCENT, DoomOverlay, DoomOverlayChrome, DoomOverlayTui };
|
|
84
|
+
//# sourceMappingURL=doomOverlay.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomOverlay.d.mts","names":[],"sources":["../../src/tui/doomOverlay.ts"],"mappings":";;;UA8BiB;EACf;IACE;IACA;;EAEF,cAAc;;UAGC;EACf;EACA;EACA;EACA;EACA;;;;;;EAMA;;;;;;EAMA,SAAS,WAAW;;;;;;;;cAST;;cAGA;WAAA;WAAA;;cAKA;;;;;;;;;uBAoBS,uBAAuB;qBAEtB,KAAK;qBACL,OAAO;YAFnB,YACY,KAAK,gBACL,OAAO;EAG5B;EAIA,OAAO;qBA4BY,aAAa;qBAEb,WAAW,eAAe;;;;;;UAOrC;UAcA;UAUA;UAqBA;;;;;;UAYA;;;;;;UAkBA;UAMA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{fitLine as e,frameLine as t,padLine as n}from"./rendering.mjs";import{truncateToWidth as r,visibleWidth as i}from"@earendil-works/pi-tui";const a=`borderMuted`,o=`accent`,s=[`╭`,`╮`],c=[`├`,`┤`],l=[`╰`,`╯`],u=`mdHeading`,d={list:`↑↓`,detail:`JK`},f={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}};function p(e,t){return!e||t<=0||t<Math.min(i(e),8)?``:r(e,t,`…`)}var m=class{tui;theme;constructor(e,t){this.tui=e,this.theme=t}invalidate(){this.tui.requestRender()}render(e){let t=Math.max(0,Math.floor(e)),r=Math.max(0,Math.floor(this.tui.terminal?.rows??24));if(t===0||r===0)return[];let i=this.getChrome();if(r<6||t<8)return this.renderCompact(i,t,r);let o=t-2,u=o-2,d=r-6,f=this.renderBody(u,d).slice(0,d);for(;f.length<d;)f.push(``);let p=i.accent??a;return[this.borderRow(s,o,p),this.contentRow(this.headerRow(i,u),u,t,p),this.borderRow(c,o,p,a),...f.map(e=>this.contentRow(e,u,t,p)),this.borderRow(c,o,p,a),this.contentRow(this.footerRow(i,u),u,t,p),this.borderRow(l,o,p)].map(e=>n(e,t))}borderRow([e,t],n,r=a,i=r){let o=r??a;return[this.theme.fg(o,e),this.theme.fg(i??o,`─`.repeat(n)),this.theme.fg(o,t)].join(``)}contentRow(e,r,i,o=a){let s=this.theme.fg(o??a,`│`);return t(` ${n(e,r)} `,i,s,s)}headerRow(e,t){let n=e.accent,a=n?t-2:t,s=r(e.title,Math.max(0,a),`…`),c=n?this.theme.inverse(this.theme.bold(this.theme.fg(n,` ${s} `))):this.theme.bold(this.theme.fg(o,s)),l=i(s)+(n?2:0),u=p(e.breadcrumb,t-l-2);return u&&(c+=`${` `.repeat(2)}${this.theme.fg(`dim`,u)}`,l+=2+i(u)),this.withRightStatus(c,l,e.headerRight,t)}footerRow(e,t){let n=e.footerHints?.length?this.hintLegend(e.footerHints,t):this.theme.fg(`dim`,r(e.footer,t,`…`));return this.withRightStatus(n,i(n),e.footerRight,t)}hintLegend(e,t){let n=``,r=0;for(let[a,o]of e){let e=i(a)+i(o)+6;if(r+e>t)break;let s=this.theme.bg(`selectedBg`,this.theme.fg(`text`,` ${a} `));n+=`${n?` `:``}${s}${this.theme.fg(`dim`,` ${o}`)}`,r+=e}return n}withRightStatus(e,t,n,r){let a=n?i(n):0;return!n||a===0||t+2+a>r?e:`${e}${` `.repeat(r-t-a)}${this.theme.fg(`dim`,n)}`}renderCompact(t,r,i){let a=Array.from({length:i},()=>``);return a[0]=this.theme.bold(this.theme.fg(o,t.title)),i>1&&(a[i-1]=this.theme.fg(`dim`,t.footer)),a.map(t=>n(e(t,r),r))}};export{f as DOOM_FULLSCREEN_UI_OPTIONS,d as DOOM_NAVIGATION_KEYS,u as DOOM_OVERLAY_ACCENT,m as DoomOverlay};
|
|
2
|
+
//# sourceMappingURL=doomOverlay.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomOverlay.mjs","names":[],"sources":["../../src/tui/doomOverlay.ts"],"sourcesContent":["import type { Theme } from '@earendil-works/pi-coding-agent';\nimport { type Component, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { fitLine, frameLine, padLine } from './rendering.ts';\n\nconst DEFAULT_TERMINAL_ROWS = 24;\nconst FULL_CHROME_ROWS = 6;\n/** Two frame columns and two gutter columns, leaving content worth framing. */\nconst MIN_FRAMED_WIDTH = 8;\nconst GUTTER = ' ';\nconst GUTTER_COLUMNS = 2;\n/** Blank columns kept between a row's left cluster and its right-aligned status. */\nconst ROW_GAP = 2;\n/** A secondary segment thinner than this is dropped rather than shredded into a stub. */\nconst MIN_SEGMENT_WIDTH = 8;\nconst ELLIPSIS = '…';\nconst BORDER_COLOR = 'borderMuted';\nconst TEXT_COLOR = 'text';\n/** Filled key caps read as pressable, matching the doom mockups' footers. */\nconst HINT_CAP_BACKGROUND = 'selectedBg';\nconst HINT_SEPARATOR = ' ';\n/** A cap's two pad columns plus the space before its label. */\nconst HINT_CHROME_COLUMNS = 6;\nconst TITLE_COLOR = 'accent';\nconst SECONDARY_COLOR = 'dim';\nconst HORIZONTAL_BORDER = '─';\nconst VERTICAL_BORDER = '│';\nconst TOP_CORNERS = ['╭', '╮'] as const;\nconst DIVIDER_JOINTS = ['├', '┤'] as const;\nconst BOTTOM_CORNERS = ['╰', '╯'] as const;\n\nexport interface DoomOverlayTui {\n terminal?: {\n rows: number;\n columns?: number;\n };\n requestRender(force?: boolean): void;\n}\n\nexport interface DoomOverlayChrome {\n title: string;\n breadcrumb?: string;\n headerRight?: string;\n footer: string;\n footerRight?: string;\n /**\n * Key/label pairs rendered as filled caps, e.g. `[ enter ] edit subject`.\n * Takes precedence over `footer`, which stays for surfaces that want a plain\n * sentence rather than a key legend.\n */\n footerHints?: readonly (readonly [string, string])[];\n /**\n * Signature colour for the surface. When set, the frame takes it and the\n * title renders as a filled badge rather than plain accent text, matching the\n * doom mockups. Left unset, the frame stays muted and the title plain.\n */\n accent?: Parameters<Theme['fg']>[0];\n}\n\n/**\n * One signature colour for every doom overlay: the frame and the title badge.\n * Per-surface colours were tried and read as arbitrary, and they also borrowed\n * tokens that mean something else (`warning`, `syntaxNumber`), so a theme edit\n * elsewhere would have repainted a frame. Change it here to change them all.\n */\nexport const DOOM_OVERLAY_ACCENT = 'mdHeading' satisfies DoomOverlayChrome['accent'];\n\n/** Canonical legend tokens for list/roster navigation and detail-pane scrolling. */\nexport const DOOM_NAVIGATION_KEYS = {\n list: '↑↓',\n detail: 'JK',\n} as const;\n\nexport const DOOM_FULLSCREEN_UI_OPTIONS = {\n overlay: true,\n overlayOptions: {\n anchor: 'top-left',\n width: '100%',\n maxHeight: '100%',\n margin: 0,\n },\n} as const;\n\n/**\n * Fits a secondary segment into `budget`, or drops it entirely when too few\n * columns remain for it to stay readable. Truncating everything to fit is what\n * makes a tight header read as one run-on string.\n */\nfunction fitSegment(text: string | undefined, budget: number): string {\n if (!text || budget <= 0) return '';\n return budget < Math.min(visibleWidth(text), MIN_SEGMENT_WIDTH) ? '' : truncateToWidth(text, budget, ELLIPSIS);\n}\n\nexport abstract class DoomOverlay implements Component {\n protected constructor(\n protected readonly tui: DoomOverlayTui,\n protected readonly theme: Theme,\n ) {}\n\n invalidate(): void {\n this.tui.requestRender();\n }\n\n render(width: number): string[] {\n const safeWidth = Math.max(0, Math.floor(width));\n const height = Math.max(0, Math.floor(this.tui.terminal?.rows ?? DEFAULT_TERMINAL_ROWS));\n if (safeWidth === 0 || height === 0) return [];\n\n const chrome = this.getChrome();\n if (height < FULL_CHROME_ROWS || safeWidth < MIN_FRAMED_WIDTH) {\n return this.renderCompact(chrome, safeWidth, height);\n }\n\n const innerWidth = safeWidth - GUTTER_COLUMNS;\n const contentWidth = innerWidth - GUTTER_COLUMNS;\n const bodyHeight = height - FULL_CHROME_ROWS;\n const body = this.renderBody(contentWidth, bodyHeight).slice(0, bodyHeight);\n while (body.length < bodyHeight) body.push('');\n\n const frame = chrome.accent ?? BORDER_COLOR;\n return [\n this.borderRow(TOP_CORNERS, innerWidth, frame),\n this.contentRow(this.headerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n ...body.map((line) => this.contentRow(line, contentWidth, safeWidth, frame)),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n this.contentRow(this.footerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(BOTTOM_CORNERS, innerWidth, frame),\n ].map((line) => padLine(line, safeWidth));\n }\n\n protected abstract getChrome(): DoomOverlayChrome;\n\n protected abstract renderBody(width: number, height: number): string[];\n\n /**\n * Edge glyphs and the rule between them are coloured separately: the outer\n * rectangle carries the signature colour while an internal separator stays\n * muted, meeting the frame at accent-coloured junctions.\n */\n private borderRow(\n [left, right]: readonly [string, string],\n innerWidth: number,\n edge: DoomOverlayChrome['accent'] = BORDER_COLOR,\n fill: DoomOverlayChrome['accent'] = edge,\n ): string {\n const edgeColour = edge ?? BORDER_COLOR;\n return [\n this.theme.fg(edgeColour, left),\n this.theme.fg(fill ?? edgeColour, HORIZONTAL_BORDER.repeat(innerWidth)),\n this.theme.fg(edgeColour, right),\n ].join('');\n }\n\n private contentRow(\n content: string,\n contentWidth: number,\n width: number,\n colour: DoomOverlayChrome['accent'] = BORDER_COLOR,\n ): string {\n const edge = this.theme.fg(colour ?? BORDER_COLOR, VERTICAL_BORDER);\n return frameLine(`${GUTTER}${padLine(content, contentWidth)}${GUTTER}`, width, edge, edge);\n }\n\n private headerRow(chrome: DoomOverlayChrome, width: number): string {\n // A signature colour turns the title into a filled badge, as the mockups\n // show; without one it stays plain accent text.\n const accent = chrome.accent;\n const titleBudget = accent ? width - GUTTER_COLUMNS : width;\n const title = truncateToWidth(chrome.title, Math.max(0, titleBudget), ELLIPSIS);\n let left = accent\n ? this.theme.inverse(this.theme.bold(this.theme.fg(accent, ` ${title} `)))\n : this.theme.bold(this.theme.fg(TITLE_COLOR, title));\n let leftWidth = visibleWidth(title) + (accent ? GUTTER_COLUMNS : 0);\n\n // The breadcrumb claims room before the right status: it says where you are,\n // which outranks ambient context when the terminal is narrow.\n const breadcrumb = fitSegment(chrome.breadcrumb, width - leftWidth - ROW_GAP);\n if (breadcrumb) {\n left += `${' '.repeat(ROW_GAP)}${this.theme.fg(SECONDARY_COLOR, breadcrumb)}`;\n leftWidth += ROW_GAP + visibleWidth(breadcrumb);\n }\n return this.withRightStatus(left, leftWidth, chrome.headerRight, width);\n }\n\n private footerRow(chrome: DoomOverlayChrome, width: number): string {\n const footer = chrome.footerHints?.length\n ? this.hintLegend(chrome.footerHints, width)\n : this.theme.fg(SECONDARY_COLOR, truncateToWidth(chrome.footer, width, ELLIPSIS));\n return this.withRightStatus(footer, visibleWidth(footer), chrome.footerRight, width);\n }\n\n /**\n * Key legend with each key in a filled cap. Every segment sets its own colour\n * so the caps survive: a cap's reset would otherwise strip the surrounding\n * dim from the labels that follow it.\n */\n private hintLegend(hints: readonly (readonly [string, string])[], width: number): string {\n let legend = '';\n let used = 0;\n for (const [key, label] of hints) {\n const segmentWidth = visibleWidth(key) + visibleWidth(label) + HINT_CHROME_COLUMNS;\n if (used + segmentWidth > width) break;\n const cap = this.theme.bg(HINT_CAP_BACKGROUND, this.theme.fg(TEXT_COLOR, ` ${key} `));\n legend += `${legend ? HINT_SEPARATOR : ''}${cap}${this.theme.fg(SECONDARY_COLOR, ` ${label}`)}`;\n used += segmentWidth;\n }\n return legend;\n }\n\n /**\n * Right-aligns a status cluster, dropping it whole when the row cannot spare\n * the columns. A clipped status reads as a stub (`doom-log…`) that carries\n * less than the blank space it costs.\n */\n private withRightStatus(left: string, leftWidth: number, right: string | undefined, width: number): string {\n const statusWidth = right ? visibleWidth(right) : 0;\n if (!right || statusWidth === 0 || leftWidth + ROW_GAP + statusWidth > width) return left;\n return `${left}${' '.repeat(width - leftWidth - statusWidth)}${this.theme.fg(SECONDARY_COLOR, right)}`;\n }\n\n private renderCompact(chrome: DoomOverlayChrome, width: number, height: number): string[] {\n const lines = Array.from({ length: height }, () => '');\n lines[0] = this.theme.bold(this.theme.fg(TITLE_COLOR, chrome.title));\n if (height > 1) lines[height - 1] = this.theme.fg(SECONDARY_COLOR, chrome.footer);\n return lines.map((line) => padLine(fitLine(line, width), width));\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./rendering.cjs");let t=require("@earendil-works/pi-tui");const n=` · `,r=` `,i=`text`,a=`accent`,o=`warning`,s=`mdHeading`,c=[`mdCode`,`toolDiffAdded`,`warning`,`muted`];var l=class{theme;snapshot;context;footerData;modes;constructor(e,t,n,r,i=[]){this.theme=e,this.snapshot=t,this.context=n,this.footerData=r,this.modes=i}invalidate(){}render(t){if(t<=0||!this.snapshot.active)return[];if(t<4)return[this.theme.fg(`borderAccent`,`─`.repeat(t))];let n=Math.max(1,t-4),r=n=>this.theme.bg(`toolPendingBg`,e.padLine(` ${n}`,t)),a=this.theme.inverse(this.theme.bold(this.theme.fg(s,` LEADER `))),o=`${this.snapshot.prefix.join(` › `)} / ${this.snapshot.label}`,c=r(e.alignLine(`${a} ${this.theme.bold(this.theme.fg(i,o))}`,this.theme.fg(`dim`,`esc cancel · backspace up`),n)),l=this.snapshot.rootOptions?.map(e=>`${e.key} ${e.label}`).join(` · `),u=t>=100&&this.snapshot.prefix.length>1&&l?[r(this.theme.fg(`dim`,`root ${l}`))]:[],d=Math.max(1,19-u.length),f=this.renderOptionGrid(n).slice(0,d).map(r),p=Math.max(0,17-u.length-f.length),m=this.snapshot.prefix.length===1?this.renderDiagnostics(n).slice(0,p).map(r):[];return[...[r(``),c,r(``),...f,...u,...m].slice(0,20),r(``)]}renderOptionGrid(n){if(this.snapshot.options.length===0)return[];let o=this.snapshot.options.map(e=>({heading:`${this.theme.inverse(this.theme.bold(this.theme.fg(a,` ${e.key} `)))} ${this.theme.bold(this.theme.fg(i,e.label))}`,headingWidth:4+(0,t.visibleWidth)(e.label),detail:e.detail?`${r}${this.theme.fg(`dim`,e.detail)}`:``,detailWidth:e.detail?(0,t.visibleWidth)(r)+(0,t.visibleWidth)(e.detail):0})),s=Math.max(...o.map(e=>Math.max(e.headingWidth,e.detailWidth))),c=Math.min(n,s),l=Math.max(1,Math.floor((n+2)/(c+2))),u=` `.repeat(2),d=[];for(let t=0;t<o.length;t+=l){let r=o.slice(t,t+l);d.push(e.fitLine(r.map(t=>e.padLine(t.heading,c)).join(u),n)),d.push(e.fitLine(r.map(t=>e.padLine(t.detail,c)).join(u),n))}return d}renderDiagnostics(e){let t=this.sessionDiagnostics(),n=this.extensionDiagnostics(e),r=this.modeDiagnostics();if(!t&&!r&&n.length===0)return[];let i=[this.theme.fg(`borderMuted`,`─`.repeat(e))];r&&i.push(this.diagnosticLine(`MODES`,r,o)),t&&i.push(this.diagnosticLine(`SESSION`,t,s));for(let[e,t]of n.entries()){let n=e===0;i.push(this.diagnosticLine(n?`EXTENSIONS`:`↳`,t,n?a:`dim`))}return i}diagnosticLine(e,t,n){return`${this.theme.bold(this.theme.fg(n,e.padEnd(11)))}${t}`}modeDiagnostics(){if(this.modes.length!==0)return this.modes.map(e=>{let t=this.theme.fg(o,e.label.toLowerCase());return e.detail?`${t}${this.theme.fg(`dim`,` (${e.detail})`)}`:t}).join(this.theme.fg(`dim`,n))}sessionDiagnostics(){if(!this.context)return;let t=0,r=0,i=0,a=0,s=0;for(let e of this.context.sessionManager.getBranch())e.type===`message`&&e.message.role===`assistant`&&(t+=e.message.usage.input,r+=e.message.usage.output,i+=e.message.usage.cacheRead,a+=e.message.usage.cacheWrite,s+=e.message.usage.cost.total);let c=t+i+a,l=c===0?0:i/c*100,u=this.context.getContextUsage(),d=u?.percent===null||u?.percent===void 0?`ctx ?`:`ctx ${u.percent.toFixed(1)}% / ${e.formatTokens(u.contextWindow)}`;return`${this.theme.fg(`muted`,`↑${e.formatTokens(t)} ↓${e.formatTokens(r)} R${e.formatTokens(i)} W${e.formatTokens(a)} CH${l.toFixed(1)}%`)} ${this.theme.fg(o,`$${s.toFixed(3)}`)}${this.theme.fg(`dim`,`${n}${d}`)}`}extensionDiagnostics(t){if(!this.footerData)return[];let r=[...this.footerData.getExtensionStatuses().values()],i=this.footerData.getAvailableProviderCount(),a=[...r];i>0&&a.push(`${i} provider${i===1?``:`s`}`);let o=a.map((e,t)=>this.theme.fg(c[t%c.length],e)),s=Math.max(1,t-15);return e.packSegments(o,s,this.theme.fg(`dim`,n)).slice(0,2)}};exports.LeaderHints=l;
|
|
2
|
+
//# sourceMappingURL=leaderHints.cjs.map
|