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,214 +1,214 @@
1
- /**
2
- * Canonical lifecycle / tool / work status. OpenCode and OMO use different
3
- * synonyms; every mapper in the panel goes through this file. The raw status
4
- * strings live in `constants/pware.oc.core.constants.status.js`.
5
- */
6
- import {
7
- STATUS_ABANDONED,
8
- STATUS_ARCHIVED,
9
- STATUS_COMPLETED,
10
- STATUS_ERROR,
11
- STATUS_IDLE,
12
- STATUS_PAUSED,
13
- STATUS_PENDING,
14
- STATUS_RUNNING,
15
- STATUS_UNKNOWN,
16
- type CanonicalStatus,
17
- type ToolStatus,
18
- } from "./constants/pware.oc.core.constants.status.js"
19
- import { formatTokenRate } from "./pware.oc.core.pulse.js"
20
- import { clipMiddleWidth } from "./pware.oc.core.width.js"
21
-
22
- export type { CanonicalStatus, ToolStatus }
23
-
24
- /** The transient one-line status a tab shows while it waits for its data. */
25
- export type TabTone = "loading" | "error" | "muted"
26
-
27
- /** Null when the tab's data is ready — the row does not exist then. */
28
- export type TabStatus = { label: string; tone: TabTone } | null
29
-
30
- /** The transient DB state right after a session is selected but its row is not visible yet. */
31
- export const TAB_STATUS_SESSION_NOT_IN_DB = "session not in db yet"
32
-
33
- /** A tab status resolved to a render line: static glyph + label + tone. Null = no row. */
34
- export type TabStatusLine = {
35
- label: string
36
- tone: TabTone
37
- /** Static prefix glyph for error/muted; null for loading — the row animates the spinner. */
38
- glyph: "×" | "•" | null
39
- } | null
40
-
41
- export function normalizeStatus(raw: string | null | undefined): CanonicalStatus {
42
- const s = (raw || "").toLowerCase()
43
- if (s === "running" || s === "in_progress" || s === "active") return STATUS_RUNNING
44
- if (s === "completed" || s === "done" || s === "success") return STATUS_COMPLETED
45
- if (s === "error" || s === "failed") return STATUS_ERROR
46
- if (s === "pending" || s === "queued") return STATUS_PENDING
47
- if (s === "paused") return STATUS_PAUSED
48
- if (s === "abandoned") return STATUS_ABANDONED
49
- if (s === "archived") return STATUS_ARCHIVED
50
- if (s === "idle") return STATUS_IDLE
51
- return STATUS_UNKNOWN
52
- }
53
-
54
- export function toToolStatus(raw: string | null | undefined): ToolStatus {
55
- const c = normalizeStatus(raw)
56
- if (c === STATUS_RUNNING) return STATUS_RUNNING
57
- if (c === STATUS_COMPLETED) return STATUS_COMPLETED
58
- if (c === STATUS_ERROR) return STATUS_ERROR
59
- return STATUS_PENDING
60
- }
61
-
62
- /** Short work status for the panel — no full paths, no checklist text. */
63
- export function toWorkLabel(raw: string | null | undefined): string {
64
- const c = normalizeStatus(raw)
65
- if (c === STATUS_COMPLETED) return "done"
66
- if (c === STATUS_UNKNOWN) return (raw || "").toLowerCase() || "unknown"
67
- return c
68
- }
69
-
70
- /**
71
- * One-cell glyph for a work row's status: ✓ done, × error, ⧗ pending/queued,
72
- * ║ paused, ⊘ abandoned, ○ unknown. Null while running — the pulse spinner owns
73
- * the running cell. Core owns this mapping; the ui layer re-exports it.
74
- */
75
- export function workStatusGlyph(status: string): string | null {
76
- const s = toWorkLabel(status)
77
- if (s === "done") return "✓"
78
- if (s === STATUS_ERROR) return "×"
79
- if (s === STATUS_RUNNING) return null
80
- // A single-cell geometric hourglass, not the emoji — one cell, monochrome.
81
- if (s === STATUS_PENDING) return "⧗"
82
- if (s === STATUS_PAUSED) return "║"
83
- if (s === STATUS_ABANDONED) return "⊘"
84
- return "○"
85
- }
86
-
87
- export function sessionStatusLabel(status: string): string {
88
- return status
89
- }
90
-
91
- /**
92
- * The one shared decision behind every tab's status row. Priority order:
93
- * a session switch in flight, then the transient "row not visible yet" state,
94
- * then any real DB error, then Perf's own states, then a cold (not yet loaded)
95
- * tab. Null when the tab's data is ready.
96
- */
97
- export function tabStatus(opts: {
98
- tab: string
99
- currentId: string | null
100
- dbError: string | null
101
- dbPresent: boolean
102
- switching: string | null
103
- perfError: string | null
104
- perfTurns: number
105
- cold: boolean
106
- }): TabStatus {
107
- if (opts.switching && opts.currentId !== opts.switching && !opts.dbError) {
108
- return { label: `switching · ${clipMiddleWidth(opts.switching, 10)}`, tone: "loading" }
109
- }
110
- if (opts.dbError === TAB_STATUS_SESSION_NOT_IN_DB) {
111
- return { label: "waiting for session", tone: "loading" }
112
- }
113
- if (opts.dbError) {
114
- return { label: opts.dbError, tone: "error" }
115
- }
116
- if (!opts.dbPresent) {
117
- return { label: "waiting for session", tone: "loading" }
118
- }
119
- if (opts.tab === "perf") {
120
- if (opts.perfError) return { label: opts.perfError, tone: "error" }
121
- if (opts.cold) return { label: "loading · stats", tone: "loading" }
122
- if (opts.perfTurns === 0) return { label: "no turns yet", tone: "muted" }
123
- return null
124
- }
125
- if (opts.cold) {
126
- return {
127
- label: opts.tab === "mywork" ? "loading · my work" : "loading · sessions",
128
- tone: "loading",
129
- }
130
- }
131
- return null
132
- }
133
-
134
- /**
135
- * Resolve a tab status to its render line. The null contract lives here so it
136
- * is unit-testable: a ready tab (`null`) returns `null`, and the glyph maps
137
- * tone → `×` error, `•` muted, `null` loading (the row animates the spinner).
138
- */
139
- export function tabStatusLine(status: TabStatus): TabStatusLine {
140
- if (!status) return null
141
- if (status.tone === "error") return { label: status.label, tone: "error", glyph: "×" }
142
- if (status.tone === "muted") return { label: status.label, tone: "muted", glyph: "•" }
143
- return { label: status.label, tone: "loading", glyph: null }
144
- }
145
-
146
- /** Tone of the global status bar: loading/error/muted, or "ready" when a tab is done. */
147
- export type StatusBarTone = TabTone | "ready"
148
-
149
- /**
150
- * Global status-bar line. A ready tab (`null`) still yields a line — with an
151
- * empty label the bar is a bare `OES` (the realtime category tabs on the same
152
- * row already lead with their own `•`), never `OES •` + a tab bullet.
153
- */
154
- export type StatusBarLine = { label: string; tone: StatusBarTone }
155
-
156
- export function statusBarLine(status: TabStatus): StatusBarLine {
157
- const line = tabStatusLine(status)
158
- if (!line) return { label: "", tone: "ready" }
159
- return { label: line.label, tone: line.tone }
160
- }
161
-
162
- /**
163
- * The OES bar split into the pieces the row draws separately, because the
164
- * inline trend renders faded while the reading next to it stays full strength.
165
- * Empty `bar` / `rate` mean the row is a single run of text. `rateIdle` marks
166
- * a reading of zero, which the row fades to match the trend behind it.
167
- */
168
- export type OesBarParts = { head: string; bar: string; rate: string; rateIdle: boolean }
169
-
170
- /**
171
- * What the OES bar says. A busy label always wins — while the bar is reporting
172
- * a load, a switch or an error, the live token reading is noise and is dropped.
173
- * On a quiet bar the trend and the rate follow a bare `OES`; with neither, the
174
- * row is `OES` plus whatever glyph the tone asked for. Pass `rate` as the raw
175
- * per-second number; idleness follows the number the row actually prints, so a
176
- * trickle that rounds down to `0 tok/s` fades like a dead stop.
177
- */
178
- export function oesBarParts(
179
- label: string,
180
- glyph: string,
181
- rateBar = "",
182
- rate?: number | null,
183
- ): OesBarParts {
184
- if (label) return { head: `OES ${glyph} ${label}`, bar: "", rate: "", rateIdle: false }
185
- if (rateBar || rate !== undefined) {
186
- const shown = typeof rate === "number" && Number.isFinite(rate) ? Math.round(rate) : 0
187
- return {
188
- head: "OES ",
189
- bar: rateBar,
190
- rate: rate === undefined ? "" : ` ${formatTokenRate(rate)}`,
191
- rateIdle: shown <= 0,
192
- }
193
- }
194
- return { head: glyph ? `OES ${glyph}` : "OES", bar: "", rate: "", rateIdle: false }
195
- }
196
-
197
- /** Paused and abandoned are deliberate stops — they must not keep pulsing. */
198
- export function workIsTerminal(status: string): boolean {
199
- const s = toWorkLabel(status)
200
- return s === "done" || s === STATUS_ERROR || s === STATUS_PAUSED || s === STATUS_ABANDONED
201
- }
202
-
203
- export function isRunningLifecycle(raw: string | null | undefined): boolean {
204
- return normalizeStatus(raw) === STATUS_RUNNING
205
- }
206
-
207
- export function taskRank(status: string): number {
208
- const c = normalizeStatus(status)
209
- if (c === STATUS_RUNNING) return 0
210
- if (c === STATUS_PENDING) return 1
211
- if (c === STATUS_ERROR || (status || "").toLowerCase() === "cancelled") return 2
212
- if (c === STATUS_COMPLETED) return 3
213
- return 4
214
- }
1
+ /**
2
+ * Canonical lifecycle / tool / work status. OpenCode and OMO use different
3
+ * synonyms; every mapper in the panel goes through this file. The raw status
4
+ * strings live in `constants/pware.oc.core.constants.status.js`.
5
+ */
6
+ import {
7
+ STATUS_ABANDONED,
8
+ STATUS_ARCHIVED,
9
+ STATUS_COMPLETED,
10
+ STATUS_ERROR,
11
+ STATUS_IDLE,
12
+ STATUS_PAUSED,
13
+ STATUS_PENDING,
14
+ STATUS_RUNNING,
15
+ STATUS_UNKNOWN,
16
+ type CanonicalStatus,
17
+ type ToolStatus,
18
+ } from "./constants/pware.oc.core.constants.status.js"
19
+ import { formatTokenRate } from "./pware.oc.core.pulse.js"
20
+ import { clipMiddleWidth } from "./pware.oc.core.width.js"
21
+
22
+ export type { CanonicalStatus, ToolStatus }
23
+
24
+ /** The transient one-line status a tab shows while it waits for its data. */
25
+ export type TabTone = "loading" | "error" | "muted"
26
+
27
+ /** Null when the tab's data is ready — the row does not exist then. */
28
+ export type TabStatus = { label: string; tone: TabTone } | null
29
+
30
+ /** The transient DB state right after a session is selected but its row is not visible yet. */
31
+ export const TAB_STATUS_SESSION_NOT_IN_DB = "session not in db yet"
32
+
33
+ /** A tab status resolved to a render line: static glyph + label + tone. Null = no row. */
34
+ export type TabStatusLine = {
35
+ label: string
36
+ tone: TabTone
37
+ /** Static prefix glyph for error/muted; null for loading — the row animates the spinner. */
38
+ glyph: "×" | "•" | null
39
+ } | null
40
+
41
+ export function normalizeStatus(raw: string | null | undefined): CanonicalStatus {
42
+ const s = (raw || "").toLowerCase()
43
+ if (s === "running" || s === "in_progress" || s === "active") return STATUS_RUNNING
44
+ if (s === "completed" || s === "done" || s === "success") return STATUS_COMPLETED
45
+ if (s === "error" || s === "failed") return STATUS_ERROR
46
+ if (s === "pending" || s === "queued") return STATUS_PENDING
47
+ if (s === "paused") return STATUS_PAUSED
48
+ if (s === "abandoned") return STATUS_ABANDONED
49
+ if (s === "archived") return STATUS_ARCHIVED
50
+ if (s === "idle") return STATUS_IDLE
51
+ return STATUS_UNKNOWN
52
+ }
53
+
54
+ export function toToolStatus(raw: string | null | undefined): ToolStatus {
55
+ const c = normalizeStatus(raw)
56
+ if (c === STATUS_RUNNING) return STATUS_RUNNING
57
+ if (c === STATUS_COMPLETED) return STATUS_COMPLETED
58
+ if (c === STATUS_ERROR) return STATUS_ERROR
59
+ return STATUS_PENDING
60
+ }
61
+
62
+ /** Short work status for the panel — no full paths, no checklist text. */
63
+ export function toWorkLabel(raw: string | null | undefined): string {
64
+ const c = normalizeStatus(raw)
65
+ if (c === STATUS_COMPLETED) return "done"
66
+ if (c === STATUS_UNKNOWN) return (raw || "").toLowerCase() || "unknown"
67
+ return c
68
+ }
69
+
70
+ /**
71
+ * One-cell glyph for a work row's status: ✓ done, × error, ⧗ pending/queued,
72
+ * ║ paused, ⊘ abandoned, ○ unknown. Null while running — the pulse spinner owns
73
+ * the running cell. Core owns this mapping; the ui layer re-exports it.
74
+ */
75
+ export function workStatusGlyph(status: string): string | null {
76
+ const s = toWorkLabel(status)
77
+ if (s === "done") return "✓"
78
+ if (s === STATUS_ERROR) return "×"
79
+ if (s === STATUS_RUNNING) return null
80
+ // A single-cell geometric hourglass, not the emoji — one cell, monochrome.
81
+ if (s === STATUS_PENDING) return "⧗"
82
+ if (s === STATUS_PAUSED) return "║"
83
+ if (s === STATUS_ABANDONED) return "⊘"
84
+ return "○"
85
+ }
86
+
87
+ export function sessionStatusLabel(status: string): string {
88
+ return status
89
+ }
90
+
91
+ /**
92
+ * The one shared decision behind every tab's status row. Priority order:
93
+ * a session switch in flight, then the transient "row not visible yet" state,
94
+ * then any real DB error, then Perf's own states, then a cold (not yet loaded)
95
+ * tab. Null when the tab's data is ready.
96
+ */
97
+ export function tabStatus(opts: {
98
+ tab: string
99
+ currentId: string | null
100
+ dbError: string | null
101
+ dbPresent: boolean
102
+ switching: string | null
103
+ perfError: string | null
104
+ perfTurns: number
105
+ cold: boolean
106
+ }): TabStatus {
107
+ if (opts.switching && opts.currentId !== opts.switching && !opts.dbError) {
108
+ return { label: `switching · ${clipMiddleWidth(opts.switching, 10)}`, tone: "loading" }
109
+ }
110
+ if (opts.dbError === TAB_STATUS_SESSION_NOT_IN_DB) {
111
+ return { label: "waiting for session", tone: "loading" }
112
+ }
113
+ if (opts.dbError) {
114
+ return { label: opts.dbError, tone: "error" }
115
+ }
116
+ if (!opts.dbPresent) {
117
+ return { label: "waiting for session", tone: "loading" }
118
+ }
119
+ if (opts.tab === "perf") {
120
+ if (opts.perfError) return { label: opts.perfError, tone: "error" }
121
+ if (opts.cold) return { label: "loading · stats", tone: "loading" }
122
+ if (opts.perfTurns === 0) return { label: "no turns yet", tone: "muted" }
123
+ return null
124
+ }
125
+ if (opts.cold) {
126
+ return {
127
+ label: opts.tab === "mywork" ? "loading · my work" : "loading · sessions",
128
+ tone: "loading",
129
+ }
130
+ }
131
+ return null
132
+ }
133
+
134
+ /**
135
+ * Resolve a tab status to its render line. The null contract lives here so it
136
+ * is unit-testable: a ready tab (`null`) returns `null`, and the glyph maps
137
+ * tone → `×` error, `•` muted, `null` loading (the row animates the spinner).
138
+ */
139
+ export function tabStatusLine(status: TabStatus): TabStatusLine {
140
+ if (!status) return null
141
+ if (status.tone === "error") return { label: status.label, tone: "error", glyph: "×" }
142
+ if (status.tone === "muted") return { label: status.label, tone: "muted", glyph: "•" }
143
+ return { label: status.label, tone: "loading", glyph: null }
144
+ }
145
+
146
+ /** Tone of the global status bar: loading/error/muted, or "ready" when a tab is done. */
147
+ export type StatusBarTone = TabTone | "ready"
148
+
149
+ /**
150
+ * Global status-bar line. A ready tab (`null`) still yields a line — with an
151
+ * empty label the bar is a bare `OES` (the realtime category tabs on the same
152
+ * row already lead with their own `•`), never `OES •` + a tab bullet.
153
+ */
154
+ export type StatusBarLine = { label: string; tone: StatusBarTone }
155
+
156
+ export function statusBarLine(status: TabStatus): StatusBarLine {
157
+ const line = tabStatusLine(status)
158
+ if (!line) return { label: "", tone: "ready" }
159
+ return { label: line.label, tone: line.tone }
160
+ }
161
+
162
+ /**
163
+ * The OES bar split into the pieces the row draws separately, because the
164
+ * inline trend renders faded while the reading next to it stays full strength.
165
+ * Empty `bar` / `rate` mean the row is a single run of text. `rateIdle` marks
166
+ * a reading of zero, which the row fades to match the trend behind it.
167
+ */
168
+ export type OesBarParts = { head: string; bar: string; rate: string; rateIdle: boolean }
169
+
170
+ /**
171
+ * What the OES bar says. A busy label always wins — while the bar is reporting
172
+ * a load, a switch or an error, the live token reading is noise and is dropped.
173
+ * On a quiet bar the trend and the rate follow a bare `OES`; with neither, the
174
+ * row is `OES` plus whatever glyph the tone asked for. Pass `rate` as the raw
175
+ * per-second number; idleness follows the number the row actually prints, so a
176
+ * trickle that rounds down to `0 tok/s` fades like a dead stop.
177
+ */
178
+ export function oesBarParts(
179
+ label: string,
180
+ glyph: string,
181
+ rateBar = "",
182
+ rate?: number | null,
183
+ ): OesBarParts {
184
+ if (label) return { head: `OES ${glyph} ${label}`, bar: "", rate: "", rateIdle: false }
185
+ if (rateBar || rate !== undefined) {
186
+ const shown = typeof rate === "number" && Number.isFinite(rate) ? Math.round(rate) : 0
187
+ return {
188
+ head: "OES ",
189
+ bar: rateBar,
190
+ rate: rate === undefined ? "" : ` ${formatTokenRate(rate)}`,
191
+ rateIdle: shown <= 0,
192
+ }
193
+ }
194
+ return { head: glyph ? `OES ${glyph}` : "OES", bar: "", rate: "", rateIdle: false }
195
+ }
196
+
197
+ /** Paused and abandoned are deliberate stops — they must not keep pulsing. */
198
+ export function workIsTerminal(status: string): boolean {
199
+ const s = toWorkLabel(status)
200
+ return s === "done" || s === STATUS_ERROR || s === STATUS_PAUSED || s === STATUS_ABANDONED
201
+ }
202
+
203
+ export function isRunningLifecycle(raw: string | null | undefined): boolean {
204
+ return normalizeStatus(raw) === STATUS_RUNNING
205
+ }
206
+
207
+ export function taskRank(status: string): number {
208
+ const c = normalizeStatus(status)
209
+ if (c === STATUS_RUNNING) return 0
210
+ if (c === STATUS_PENDING) return 1
211
+ if (c === STATUS_ERROR || (status || "").toLowerCase() === "cancelled") return 2
212
+ if (c === STATUS_COMPLETED) return 3
213
+ return 4
214
+ }
@@ -1,63 +1,63 @@
1
- /**
2
- * pware.oc.core.timing
3
- *
4
- * Panel clock budgets — every interval and granularity in one place. The tick
5
- * drives the animation heartbeat; `now` and the scan debounce decide how often
6
- * rows and the DB re-read actually re-run. See docs/tick-system.md.
7
- */
8
-
9
- /** UI heartbeat — spinner/arrow animation phase. */
10
- export const TICK_MS = 50
11
-
12
- /**
13
- * Fast glyph cadence — spinners and direction flows step this often on a
14
- * separate signal (`glyphFrame`), so the animation is smooth while rows and
15
- * ages still run on the coarse `TICK_MS` / `NOW_MS` clocks.
16
- */
17
- export const GLYPH_TICK_MS = 80
18
-
19
- /**
20
- * Coarse `now` granularity — ages, marks and row arrays recompute at most this
21
- * often, while glyphs still animate at `TICK_MS`. Displayed ages use 1s
22
- * granularity anyway (`formatCoarseSec`).
23
- */
24
- export const NOW_MS = 1_000
25
-
26
- /** FPS read cadence in ticks (TICK_MS × 6 = 300ms). */
27
- export const FPS_READ_EVERY_TICKS = 6
28
-
29
- /** Blink half-period in ticks (TICK_MS × 2 = 100ms). */
30
- export const BLINK_TICKS = 2
31
-
32
- /** Monitor: SQLite poll interval. */
33
- export const MONITOR_POLL_MS = 1_500
34
-
35
- /** Monitor: fs-watch → emit debounce. */
36
- export const MONITOR_WATCH_DEBOUNCE_MS = 120
37
-
38
- /**
39
- * My-work attention scan cadence while the tab is quiet — the periodic floor.
40
- * DB-change-driven rescans (see MYWORK_BADGE_COOLDOWN_MS) refresh it sooner.
41
- */
42
- export const MYWORK_BADGE_MS = 5_000
43
-
44
- /**
45
- * Minimum gap between My-work badge rescans when a new DB snapshot just landed
46
- * — a question appears within ~1s instead of waiting for the 5s floor.
47
- */
48
- export const MYWORK_BADGE_COOLDOWN_MS = 1_000
49
-
50
- /** Host event → scan debounce (trailing). */
51
- export const EVENT_SCAN_DEBOUNCE_MS = 100
52
-
53
- /** Sliding window over which the live token rate is measured. */
54
- export const TOKEN_RATE_WINDOW_MS = 5_000
55
-
56
- /** How long the shared realtime timeline stays in RAM (3 minutes). */
57
- export const REALTIME_WINDOW_MS = 3 * 60 * 1000
58
-
59
- /** Window over which token/cache rates and CPU% are derived (≈ "rate right now"). */
60
- export const REALTIME_RATE_WINDOW_MS = 1_000
61
-
62
- /** Session-switch status row safety: hide the indicator even if no snapshot lands. */
63
- export const SWITCH_TIMEOUT_MS = 3_000
1
+ /**
2
+ * pware.oc.core.timing
3
+ *
4
+ * Panel clock budgets — every interval and granularity in one place. The tick
5
+ * drives the animation heartbeat; `now` and the scan debounce decide how often
6
+ * rows and the DB re-read actually re-run. See docs/tick-system.md.
7
+ */
8
+
9
+ /** UI heartbeat — spinner/arrow animation phase. */
10
+ export const TICK_MS = 50
11
+
12
+ /**
13
+ * Fast glyph cadence — spinners and direction flows step this often on a
14
+ * separate signal (`glyphFrame`), so the animation is smooth while rows and
15
+ * ages still run on the coarse `TICK_MS` / `NOW_MS` clocks.
16
+ */
17
+ export const GLYPH_TICK_MS = 80
18
+
19
+ /**
20
+ * Coarse `now` granularity — ages, marks and row arrays recompute at most this
21
+ * often, while glyphs still animate at `TICK_MS`. Displayed ages use 1s
22
+ * granularity anyway (`formatCoarseSec`).
23
+ */
24
+ export const NOW_MS = 1_000
25
+
26
+ /** FPS read cadence in ticks (TICK_MS × 6 = 300ms). */
27
+ export const FPS_READ_EVERY_TICKS = 6
28
+
29
+ /** Blink half-period in ticks (TICK_MS × 2 = 100ms). */
30
+ export const BLINK_TICKS = 2
31
+
32
+ /** Monitor: SQLite poll interval. */
33
+ export const MONITOR_POLL_MS = 1_500
34
+
35
+ /** Monitor: fs-watch → emit debounce. */
36
+ export const MONITOR_WATCH_DEBOUNCE_MS = 120
37
+
38
+ /**
39
+ * My-work attention scan cadence while the tab is quiet — the periodic floor.
40
+ * DB-change-driven rescans (see MYWORK_BADGE_COOLDOWN_MS) refresh it sooner.
41
+ */
42
+ export const MYWORK_BADGE_MS = 5_000
43
+
44
+ /**
45
+ * Minimum gap between My-work badge rescans when a new DB snapshot just landed
46
+ * — a question appears within ~1s instead of waiting for the 5s floor.
47
+ */
48
+ export const MYWORK_BADGE_COOLDOWN_MS = 1_000
49
+
50
+ /** Host event → scan debounce (trailing). */
51
+ export const EVENT_SCAN_DEBOUNCE_MS = 100
52
+
53
+ /** Sliding window over which the live token rate is measured. */
54
+ export const TOKEN_RATE_WINDOW_MS = 5_000
55
+
56
+ /** How long the shared realtime timeline stays in RAM (3 minutes). */
57
+ export const REALTIME_WINDOW_MS = 3 * 60 * 1000
58
+
59
+ /** Window over which token/cache rates and CPU% are derived (≈ "rate right now"). */
60
+ export const REALTIME_RATE_WINDOW_MS = 1_000
61
+
62
+ /** Session-switch status row safety: hide the indicator even if no snapshot lands. */
63
+ export const SWITCH_TIMEOUT_MS = 3_000