opencode-extended-sidebar 0.2.90 → 0.2.91

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.
Files changed (108) hide show
  1. package/.oesignore +17 -17
  2. package/CHANGELOG.md +96 -95
  3. package/LICENSE +21 -21
  4. package/README.md +150 -235
  5. package/oes.json +13 -11
  6. package/package.json +100 -100
  7. package/scripts/install-git-hooks.mjs +29 -29
  8. package/src/pware.oc.core/constants/index.ts +18 -18
  9. package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -29
  10. package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +15 -13
  11. package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -241
  12. package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -55
  13. package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -65
  14. package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -58
  15. package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -63
  16. package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -34
  17. package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -74
  18. package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -69
  19. package/src/pware.oc.core/git/index.ts +7 -7
  20. package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -237
  21. package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -87
  22. package/src/pware.oc.core/index.ts +19 -18
  23. package/src/pware.oc.core/pware.oc.core.bus.ts +42 -42
  24. package/src/pware.oc.core/pware.oc.core.cache.ts +48 -48
  25. package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -97
  26. package/src/pware.oc.core/pware.oc.core.debug.ts +280 -280
  27. package/src/pware.oc.core/pware.oc.core.events.ts +79 -79
  28. package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -135
  29. package/src/pware.oc.core/pware.oc.core.layout.ts +234 -234
  30. package/src/pware.oc.core/pware.oc.core.oes.ts +99 -89
  31. package/src/pware.oc.core/pware.oc.core.paths.ts +240 -240
  32. package/src/pware.oc.core/pware.oc.core.preview.ts +74 -74
  33. package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -726
  34. package/src/pware.oc.core/pware.oc.core.runtimeConfig.ts +41 -0
  35. package/src/pware.oc.core/pware.oc.core.sqlite.ts +164 -175
  36. package/src/pware.oc.core/pware.oc.core.sqliteGateway.ts +89 -0
  37. package/src/pware.oc.core/pware.oc.core.status.ts +214 -214
  38. package/src/pware.oc.core/pware.oc.core.timing.ts +63 -63
  39. package/src/pware.oc.core/pware.oc.core.width.ts +215 -215
  40. package/src/pware.oc.omo/constants/index.ts +15 -15
  41. package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -18
  42. package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -49
  43. package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -29
  44. package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -11
  45. package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -60
  46. package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -49
  47. package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -25
  48. package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -38
  49. package/src/pware.oc.omo/index.ts +7 -7
  50. package/src/pware.oc.omo/resolver/index.ts +19 -19
  51. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -179
  52. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -116
  53. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -34
  54. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -503
  55. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -40
  56. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -269
  57. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -18
  58. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -18
  59. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -155
  60. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +363 -361
  61. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -18
  62. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -11
  63. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -14
  64. package/src/pware.oc.opencode/constants/index.ts +10 -10
  65. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -15
  66. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -18
  67. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -45
  68. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -58
  69. package/src/pware.oc.opencode/index.ts +8 -8
  70. package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -182
  71. package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -399
  72. package/src/pware.oc.opencode/resolver/index.ts +223 -170
  73. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +150 -162
  74. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +149 -185
  75. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +248 -211
  76. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +22 -26
  77. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +224 -236
  78. package/src/pware.oc.perf/index.ts +14 -14
  79. package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -15
  80. package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -284
  81. package/src/pware.oc.perf/pware.oc.perf.reader.ts +962 -927
  82. package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -88
  83. package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -118
  84. package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -79
  85. package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -71
  86. package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -214
  87. package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -179
  88. package/src/pware.oc.perf/pware.oc.perf.view.tsx +463 -462
  89. package/src/pware.oc.runtime/index.ts +13 -13
  90. package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -142
  91. package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +87 -79
  92. package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +282 -253
  93. package/src/pware.oc.runtime/pware.oc.runtime.omoRead.ts +37 -0
  94. package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +83 -95
  95. package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +130 -129
  96. package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -105
  97. package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +44 -43
  98. package/src/pware.oc.runtime/resolver/index.ts +268 -224
  99. package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -146
  100. package/src/pware.oc.ui/index.ts +12 -12
  101. package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +381 -339
  102. package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -144
  103. package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -111
  104. package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -82
  105. package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -843
  106. package/src/pware.oc.ui/pware.oc.ui.sections.tsx +552 -526
  107. package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1718 -1629
  108. package/src/pware.oc.ui.tsx +98 -98
@@ -1,79 +1,79 @@
1
- /** Host event type + kind. Never reads request or response bodies. */
2
- import {
3
- EVENT_FILE_EDITED,
4
- EVENT_MESSAGE_PART_UPDATED,
5
- EVENT_PART_DELTA,
6
- EVENT_REASONING_DELTA,
7
- EVENT_REASONING_STARTED,
8
- EVENT_SESSION_CREATED,
9
- EVENT_SESSION_DIFF,
10
- EVENT_SESSION_IDLE,
11
- EVENT_SESSION_STATUS,
12
- EVENT_SESSION_UPDATED,
13
- EVENT_STEP_ENDED,
14
- EVENT_STEP_FAILED,
15
- EVENT_STEP_STARTED,
16
- EVENT_TEXT_DELTA,
17
- EVENT_TOOL_CALLED,
18
- EVENT_TOOL_ENDED,
19
- EVENT_TOOL_FAILED,
20
- EVENT_TOOL_SUCCESS,
21
- } from "./constants/pware.oc.core.constants.eventType.js"
22
-
23
- import {
24
- EVENT_KIND_DB_REFRESH,
25
- EVENT_KIND_FILE,
26
- EVENT_KIND_FLOW,
27
- EVENT_KIND_TOOL,
28
- type EventKind,
29
- } from "./constants/pware.oc.core.constants.eventKind.js"
30
-
31
- export type { EventKind }
32
-
33
- export function eventType(evt: unknown): string {
34
- if (!evt || typeof evt !== "object") return ""
35
- return String((evt as Record<string, unknown>).type ?? "").toLowerCase()
36
- }
37
-
38
- export function eventKind(type: string): EventKind | null {
39
- const t = (type || "").toLowerCase()
40
- if (!t) return null
41
- if (t.includes(EVENT_FILE_EDITED) || t.includes(EVENT_SESSION_DIFF)) return EVENT_KIND_FILE
42
- if (
43
- t.includes(EVENT_TOOL_CALLED) ||
44
- t.includes(EVENT_TOOL_SUCCESS) ||
45
- t.includes(EVENT_TOOL_FAILED) ||
46
- t.includes(EVENT_TOOL_ENDED)
47
- ) {
48
- return EVENT_KIND_TOOL
49
- }
50
- if (
51
- t.includes(EVENT_SESSION_STATUS) ||
52
- t.includes(EVENT_SESSION_IDLE) ||
53
- t.includes(EVENT_SESSION_CREATED) ||
54
- t.includes(EVENT_SESSION_UPDATED) ||
55
- t.includes(EVENT_MESSAGE_PART_UPDATED) ||
56
- t.includes(EVENT_STEP_STARTED) ||
57
- t.includes(EVENT_STEP_ENDED) ||
58
- t.includes(EVENT_STEP_FAILED)
59
- ) {
60
- return EVENT_KIND_DB_REFRESH
61
- }
62
- if (
63
- t.includes(EVENT_TEXT_DELTA) ||
64
- t.includes(EVENT_REASONING_DELTA) ||
65
- t.includes(EVENT_REASONING_STARTED) ||
66
- t.includes(EVENT_PART_DELTA) ||
67
- t.endsWith(".delta")
68
- ) {
69
- return EVENT_KIND_FLOW
70
- }
71
- return null
72
- }
73
-
74
- export function shouldRefreshDb(type: string): boolean {
75
- const t = (type || "").toLowerCase()
76
- if (!t || t.includes(".delta")) return false
77
- const kind = eventKind(t)
78
- return kind === EVENT_KIND_TOOL || kind === EVENT_KIND_FILE || kind === EVENT_KIND_DB_REFRESH
79
- }
1
+ /** Host event type + kind. Never reads request or response bodies. */
2
+ import {
3
+ EVENT_FILE_EDITED,
4
+ EVENT_MESSAGE_PART_UPDATED,
5
+ EVENT_PART_DELTA,
6
+ EVENT_REASONING_DELTA,
7
+ EVENT_REASONING_STARTED,
8
+ EVENT_SESSION_CREATED,
9
+ EVENT_SESSION_DIFF,
10
+ EVENT_SESSION_IDLE,
11
+ EVENT_SESSION_STATUS,
12
+ EVENT_SESSION_UPDATED,
13
+ EVENT_STEP_ENDED,
14
+ EVENT_STEP_FAILED,
15
+ EVENT_STEP_STARTED,
16
+ EVENT_TEXT_DELTA,
17
+ EVENT_TOOL_CALLED,
18
+ EVENT_TOOL_ENDED,
19
+ EVENT_TOOL_FAILED,
20
+ EVENT_TOOL_SUCCESS,
21
+ } from "./constants/pware.oc.core.constants.eventType.js"
22
+
23
+ import {
24
+ EVENT_KIND_DB_REFRESH,
25
+ EVENT_KIND_FILE,
26
+ EVENT_KIND_FLOW,
27
+ EVENT_KIND_TOOL,
28
+ type EventKind,
29
+ } from "./constants/pware.oc.core.constants.eventKind.js"
30
+
31
+ export type { EventKind }
32
+
33
+ export function eventType(evt: unknown): string {
34
+ if (!evt || typeof evt !== "object") return ""
35
+ return String((evt as Record<string, unknown>).type ?? "").toLowerCase()
36
+ }
37
+
38
+ export function eventKind(type: string): EventKind | null {
39
+ const t = (type || "").toLowerCase()
40
+ if (!t) return null
41
+ if (t.includes(EVENT_FILE_EDITED) || t.includes(EVENT_SESSION_DIFF)) return EVENT_KIND_FILE
42
+ if (
43
+ t.includes(EVENT_TOOL_CALLED) ||
44
+ t.includes(EVENT_TOOL_SUCCESS) ||
45
+ t.includes(EVENT_TOOL_FAILED) ||
46
+ t.includes(EVENT_TOOL_ENDED)
47
+ ) {
48
+ return EVENT_KIND_TOOL
49
+ }
50
+ if (
51
+ t.includes(EVENT_SESSION_STATUS) ||
52
+ t.includes(EVENT_SESSION_IDLE) ||
53
+ t.includes(EVENT_SESSION_CREATED) ||
54
+ t.includes(EVENT_SESSION_UPDATED) ||
55
+ t.includes(EVENT_MESSAGE_PART_UPDATED) ||
56
+ t.includes(EVENT_STEP_STARTED) ||
57
+ t.includes(EVENT_STEP_ENDED) ||
58
+ t.includes(EVENT_STEP_FAILED)
59
+ ) {
60
+ return EVENT_KIND_DB_REFRESH
61
+ }
62
+ if (
63
+ t.includes(EVENT_TEXT_DELTA) ||
64
+ t.includes(EVENT_REASONING_DELTA) ||
65
+ t.includes(EVENT_REASONING_STARTED) ||
66
+ t.includes(EVENT_PART_DELTA) ||
67
+ t.endsWith(".delta")
68
+ ) {
69
+ return EVENT_KIND_FLOW
70
+ }
71
+ return null
72
+ }
73
+
74
+ export function shouldRefreshDb(type: string): boolean {
75
+ const t = (type || "").toLowerCase()
76
+ if (!t || t.includes(".delta")) return false
77
+ const kind = eventKind(t)
78
+ return kind === EVENT_KIND_TOOL || kind === EVENT_KIND_FILE || kind === EVENT_KIND_DB_REFRESH
79
+ }
@@ -1,135 +1,135 @@
1
- /**
2
- * pware.oc.core.glyph
3
- *
4
- * Every visual glyph as one complete definition: a character plus a semantic
5
- * tone (and an optional blink flag). A glyph's char and colour can never drift
6
- * apart because they come from the same switch. `markTone` / `stateGlyph` /
7
- * `directionGlyph` are pure and theme-independent — the ui layer resolves a
8
- * `ToneKey` to an actual RGBA through `toneColor` in
9
- * `pware.oc.ui/pware.oc.ui.chrome.tsx`.
10
- */
11
- import { BLINK_TICKS } from "./pware.oc.core.timing.js"
12
- import {
13
- FLOW_RECV,
14
- FLOW_TOOL,
15
- FLOW_WAIT,
16
- MARK_QUEUED,
17
- PULSE_LIVE,
18
- PULSE_STALE,
19
- type AgentMark,
20
- type FlowDir,
21
- } from "./constants/pware.oc.core.constants.pulse.js"
22
- import { STATUS_ERROR } from "./constants/pware.oc.core.constants.status.js"
23
- import {
24
- ROW_KIND_FILE,
25
- ROW_KIND_GROUP,
26
- type RowKind,
27
- } from "./constants/pware.oc.core.constants.rowKind.js"
28
-
29
- /** Semantic colour of a glyph or body — no theme values, just meaning. */
30
- export type ToneKey = "text" | "textMuted" | "success" | "primary" | "warning" | "error"
31
-
32
- /** One glyph as a complete definition: the character, its tone, and blink. */
33
- export type GlyphSpec = {
34
- char: string
35
- tone: ToneKey
36
- blink?: boolean
37
- }
38
-
39
- /** Same braille set as OpenCode TUI thinking spinner (`opentui-spinner`). */
40
- export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const
41
-
42
- /** Queued — waiting for a slot: the hourglass (the wait flow owns ◷). */
43
- export const QUEUED_GLYPH = "⧗"
44
-
45
- export function spinnerFrame(frame: number): string {
46
- const i = ((frame % SPINNER_FRAMES.length) + SPINNER_FRAMES.length) % SPINNER_FRAMES.length
47
- return SPINNER_FRAMES[i] ?? "⠋"
48
- }
49
-
50
- /** Cold-start "engage" bar length in glyph frames (GLYPH_TICK_MS × 12 ≈ 1s). */
51
- export const ENGAGE_MIN_FRAMES = 12
52
- /** Hard ceiling so the boot bar can never strand (GLYPH_TICK_MS × 50 = 4s). */
53
- export const ENGAGE_MAX_FRAMES = 50
54
-
55
- /** Fill ratio of the cold-start engage bar: linear to 1 at `ENGAGE_MIN_FRAMES`. */
56
- export function engageFill(frame: number): number {
57
- return Math.min(1, Math.max(0, frame) / ENGAGE_MIN_FRAMES)
58
- }
59
-
60
- /**
61
- * The cold-start engage bar ends once the first real snapshot has landed
62
- * (`ready`) after its minimum show time — or on the hard ceiling (no DB / no
63
- * snapshot ever) so the row can never strand. Negative frames count as 0.
64
- */
65
- export function engageDone(frame: number, ready: boolean): boolean {
66
- const n = Math.max(0, frame)
67
- return (ready && n >= ENGAGE_MIN_FRAMES) || n >= ENGAGE_MAX_FRAMES
68
- }
69
-
70
- /** Options for {@link sinPulseAlpha} — one full fade cycle spans `periodFrames` tick frames. */
71
- export type SinPulseAlphaOpts = {
72
- periodFrames?: number
73
- min?: number
74
- max?: number
75
- }
76
-
77
- /**
78
- * Sinusoidal fade pulse for glyph opacity: alpha eases `min → max → min` over one
79
- * period. Defaults to `BLINK_TICKS * 2` frames (same wall-clock span as
80
- * {@link flowBlinkOn}). Pure — inject the UI tick frame; the ui layer maps the
81
- * result to OpenTUI `opacity` or a colour lerp.
82
- */
83
- export function sinPulseAlpha(frame: number, opts: SinPulseAlphaOpts = {}): number {
84
- const period = Math.max(1, Math.round(opts.periodFrames ?? BLINK_TICKS * 2))
85
- const lo = Math.min(opts.min ?? 0.25, opts.max ?? 1)
86
- const hi = Math.max(opts.min ?? 0.25, opts.max ?? 1)
87
- const phase = (Math.abs(frame) % period) / period
88
- const wave = Math.sin(phase * Math.PI)
89
- return lo + wave * (hi - lo)
90
- }
91
-
92
- /** ↑/↓ blink half-period in ticks (TICK_MS × BLINK_TICKS = 100ms). */
93
- export function flowBlinkOn(frame: number): boolean {
94
- return Math.floor(Math.abs(frame) / BLINK_TICKS) % 2 === 0
95
- }
96
-
97
- /** The tone a state mark carries: error red, queued/stale warning, live success, else muted. */
98
- export function markTone(mark: AgentMark): ToneKey {
99
- if (mark === STATUS_ERROR) return "error"
100
- if (mark === MARK_QUEUED || mark === PULSE_STALE) return "warning"
101
- if (mark === PULSE_LIVE) return "success"
102
- return "textMuted"
103
- }
104
-
105
- /** The state glyph for an agent mark — spinner, cross, hourglass or dot, in one spec. */
106
- export function stateGlyph(mark: AgentMark, frame = 0): GlyphSpec {
107
- const char =
108
- mark === STATUS_ERROR
109
- ? "×"
110
- : mark === MARK_QUEUED
111
- ? QUEUED_GLYPH
112
- : mark === PULSE_LIVE || mark === PULSE_STALE
113
- ? spinnerFrame(frame)
114
- : "•"
115
- return { char, tone: markTone(mark) }
116
- }
117
-
118
- /** The direction glyph for a flow — ◷ waiting, ← receiving, → tool; null when quiet. */
119
- export function directionGlyph(flow: FlowDir | null): GlyphSpec | null {
120
- if (!flow) return null
121
- if (flow === FLOW_WAIT) return { char: "◷", tone: "warning", blink: true }
122
- if (flow === FLOW_RECV) return { char: "←", tone: "success", blink: true }
123
- return { char: "→", tone: "primary", blink: true }
124
- }
125
-
126
- /** Body text tone for a row kind: quiet for files/groups, otherwise the mark tone. */
127
- export function defaultBodyTone(
128
- kind: RowKind,
129
- mark: AgentMark | undefined,
130
- current: boolean,
131
- ): ToneKey {
132
- if (kind === ROW_KIND_FILE || kind === ROW_KIND_GROUP) return "textMuted"
133
- const tone = mark ? markTone(mark) : "textMuted"
134
- return current && tone === "textMuted" ? "primary" : tone
135
- }
1
+ /**
2
+ * pware.oc.core.glyph
3
+ *
4
+ * Every visual glyph as one complete definition: a character plus a semantic
5
+ * tone (and an optional blink flag). A glyph's char and colour can never drift
6
+ * apart because they come from the same switch. `markTone` / `stateGlyph` /
7
+ * `directionGlyph` are pure and theme-independent — the ui layer resolves a
8
+ * `ToneKey` to an actual RGBA through `toneColor` in
9
+ * `pware.oc.ui/pware.oc.ui.chrome.tsx`.
10
+ */
11
+ import { BLINK_TICKS } from "./pware.oc.core.timing.js"
12
+ import {
13
+ FLOW_RECV,
14
+ FLOW_TOOL,
15
+ FLOW_WAIT,
16
+ MARK_QUEUED,
17
+ PULSE_LIVE,
18
+ PULSE_STALE,
19
+ type AgentMark,
20
+ type FlowDir,
21
+ } from "./constants/pware.oc.core.constants.pulse.js"
22
+ import { STATUS_ERROR } from "./constants/pware.oc.core.constants.status.js"
23
+ import {
24
+ ROW_KIND_FILE,
25
+ ROW_KIND_GROUP,
26
+ type RowKind,
27
+ } from "./constants/pware.oc.core.constants.rowKind.js"
28
+
29
+ /** Semantic colour of a glyph or body — no theme values, just meaning. */
30
+ export type ToneKey = "text" | "textMuted" | "success" | "primary" | "warning" | "error"
31
+
32
+ /** One glyph as a complete definition: the character, its tone, and blink. */
33
+ export type GlyphSpec = {
34
+ char: string
35
+ tone: ToneKey
36
+ blink?: boolean
37
+ }
38
+
39
+ /** Same braille set as OpenCode TUI thinking spinner (`opentui-spinner`). */
40
+ export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const
41
+
42
+ /** Queued — waiting for a slot: the hourglass (the wait flow owns ◷). */
43
+ export const QUEUED_GLYPH = "⧗"
44
+
45
+ export function spinnerFrame(frame: number): string {
46
+ const i = ((frame % SPINNER_FRAMES.length) + SPINNER_FRAMES.length) % SPINNER_FRAMES.length
47
+ return SPINNER_FRAMES[i] ?? "⠋"
48
+ }
49
+
50
+ /** Cold-start "engage" bar length in glyph frames (GLYPH_TICK_MS × 12 ≈ 1s). */
51
+ export const ENGAGE_MIN_FRAMES = 12
52
+ /** Hard ceiling so the boot bar can never strand (GLYPH_TICK_MS × 50 = 4s). */
53
+ export const ENGAGE_MAX_FRAMES = 50
54
+
55
+ /** Fill ratio of the cold-start engage bar: linear to 1 at `ENGAGE_MIN_FRAMES`. */
56
+ export function engageFill(frame: number): number {
57
+ return Math.min(1, Math.max(0, frame) / ENGAGE_MIN_FRAMES)
58
+ }
59
+
60
+ /**
61
+ * The cold-start engage bar ends once the first real snapshot has landed
62
+ * (`ready`) after its minimum show time — or on the hard ceiling (no DB / no
63
+ * snapshot ever) so the row can never strand. Negative frames count as 0.
64
+ */
65
+ export function engageDone(frame: number, ready: boolean): boolean {
66
+ const n = Math.max(0, frame)
67
+ return (ready && n >= ENGAGE_MIN_FRAMES) || n >= ENGAGE_MAX_FRAMES
68
+ }
69
+
70
+ /** Options for {@link sinPulseAlpha} — one full fade cycle spans `periodFrames` tick frames. */
71
+ export type SinPulseAlphaOpts = {
72
+ periodFrames?: number
73
+ min?: number
74
+ max?: number
75
+ }
76
+
77
+ /**
78
+ * Sinusoidal fade pulse for glyph opacity: alpha eases `min → max → min` over one
79
+ * period. Defaults to `BLINK_TICKS * 2` frames (same wall-clock span as
80
+ * {@link flowBlinkOn}). Pure — inject the UI tick frame; the ui layer maps the
81
+ * result to OpenTUI `opacity` or a colour lerp.
82
+ */
83
+ export function sinPulseAlpha(frame: number, opts: SinPulseAlphaOpts = {}): number {
84
+ const period = Math.max(1, Math.round(opts.periodFrames ?? BLINK_TICKS * 2))
85
+ const lo = Math.min(opts.min ?? 0.25, opts.max ?? 1)
86
+ const hi = Math.max(opts.min ?? 0.25, opts.max ?? 1)
87
+ const phase = (Math.abs(frame) % period) / period
88
+ const wave = Math.sin(phase * Math.PI)
89
+ return lo + wave * (hi - lo)
90
+ }
91
+
92
+ /** ↑/↓ blink half-period in ticks (TICK_MS × BLINK_TICKS = 100ms). */
93
+ export function flowBlinkOn(frame: number): boolean {
94
+ return Math.floor(Math.abs(frame) / BLINK_TICKS) % 2 === 0
95
+ }
96
+
97
+ /** The tone a state mark carries: error red, queued/stale warning, live success, else muted. */
98
+ export function markTone(mark: AgentMark): ToneKey {
99
+ if (mark === STATUS_ERROR) return "error"
100
+ if (mark === MARK_QUEUED || mark === PULSE_STALE) return "warning"
101
+ if (mark === PULSE_LIVE) return "success"
102
+ return "textMuted"
103
+ }
104
+
105
+ /** The state glyph for an agent mark — spinner, cross, hourglass or dot, in one spec. */
106
+ export function stateGlyph(mark: AgentMark, frame = 0): GlyphSpec {
107
+ const char =
108
+ mark === STATUS_ERROR
109
+ ? "×"
110
+ : mark === MARK_QUEUED
111
+ ? QUEUED_GLYPH
112
+ : mark === PULSE_LIVE || mark === PULSE_STALE
113
+ ? spinnerFrame(frame)
114
+ : "•"
115
+ return { char, tone: markTone(mark) }
116
+ }
117
+
118
+ /** The direction glyph for a flow — ◷ waiting, ← receiving, → tool; null when quiet. */
119
+ export function directionGlyph(flow: FlowDir | null): GlyphSpec | null {
120
+ if (!flow) return null
121
+ if (flow === FLOW_WAIT) return { char: "◷", tone: "warning", blink: true }
122
+ if (flow === FLOW_RECV) return { char: "←", tone: "success", blink: true }
123
+ return { char: "→", tone: "primary", blink: true }
124
+ }
125
+
126
+ /** Body text tone for a row kind: quiet for files/groups, otherwise the mark tone. */
127
+ export function defaultBodyTone(
128
+ kind: RowKind,
129
+ mark: AgentMark | undefined,
130
+ current: boolean,
131
+ ): ToneKey {
132
+ if (kind === ROW_KIND_FILE || kind === ROW_KIND_GROUP) return "textMuted"
133
+ const tone = mark ? markTone(mark) : "textMuted"
134
+ return current && tone === "textMuted" ? "primary" : tone
135
+ }