opencode-extended-sidebar 0.2.90
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/.oesignore +17 -0
- package/CHANGELOG.md +95 -0
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/oes.json +11 -0
- package/package.json +100 -0
- package/scripts/install-git-hooks.mjs +29 -0
- package/src/pware.oc.core/constants/index.ts +18 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +13 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -0
- package/src/pware.oc.core/git/index.ts +7 -0
- package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -0
- package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -0
- package/src/pware.oc.core/index.ts +18 -0
- package/src/pware.oc.core/pware.oc.core.bus.ts +42 -0
- package/src/pware.oc.core/pware.oc.core.cache.ts +48 -0
- package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -0
- package/src/pware.oc.core/pware.oc.core.debug.ts +280 -0
- package/src/pware.oc.core/pware.oc.core.events.ts +79 -0
- package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -0
- package/src/pware.oc.core/pware.oc.core.layout.ts +234 -0
- package/src/pware.oc.core/pware.oc.core.oes.ts +89 -0
- package/src/pware.oc.core/pware.oc.core.paths.ts +240 -0
- package/src/pware.oc.core/pware.oc.core.preview.ts +74 -0
- package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -0
- package/src/pware.oc.core/pware.oc.core.sqlite.ts +175 -0
- package/src/pware.oc.core/pware.oc.core.status.ts +214 -0
- package/src/pware.oc.core/pware.oc.core.timing.ts +63 -0
- package/src/pware.oc.core/pware.oc.core.width.ts +215 -0
- package/src/pware.oc.omo/constants/index.ts +15 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -0
- package/src/pware.oc.omo/index.ts +7 -0
- package/src/pware.oc.omo/resolver/index.ts +19 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +361 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -0
- package/src/pware.oc.opencode/constants/index.ts +10 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -0
- package/src/pware.oc.opencode/index.ts +8 -0
- package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -0
- package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -0
- package/src/pware.oc.opencode/resolver/index.ts +170 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +162 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +185 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +211 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +26 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +236 -0
- package/src/pware.oc.perf/index.ts +14 -0
- package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -0
- package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -0
- package/src/pware.oc.perf/pware.oc.perf.reader.ts +927 -0
- package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -0
- package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -0
- package/src/pware.oc.perf/pware.oc.perf.view.tsx +462 -0
- package/src/pware.oc.runtime/index.ts +13 -0
- package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -0
- package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +79 -0
- package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +253 -0
- package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +95 -0
- package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +129 -0
- package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -0
- package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +43 -0
- package/src/pware.oc.runtime/resolver/index.ts +224 -0
- package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -0
- package/src/pware.oc.ui/index.ts +12 -0
- package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +339 -0
- package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -0
- package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -0
- package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -0
- package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -0
- package/src/pware.oc.ui/pware.oc.ui.sections.tsx +526 -0
- package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1629 -0
- package/src/pware.oc.ui.tsx +98 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin readonly SQLite handle: prefers bun:sqlite, falls back to node:sqlite.
|
|
3
|
+
* Never writes. Returns null when neither backend / DB is available.
|
|
4
|
+
*/
|
|
5
|
+
import { createRequire } from "node:module"
|
|
6
|
+
import fs from "node:fs"
|
|
7
|
+
import { dbg, profile, pushScreenLine } from "./pware.oc.core.debug.js"
|
|
8
|
+
|
|
9
|
+
export type SqlRow = Record<string, unknown>
|
|
10
|
+
|
|
11
|
+
export type SqlDb = {
|
|
12
|
+
all: <T extends SqlRow = SqlRow>(sql: string, ...params: unknown[]) => T[]
|
|
13
|
+
get: <T extends SqlRow = SqlRow>(sql: string, ...params: unknown[]) => T | null
|
|
14
|
+
close: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const require = createRequire(import.meta.url)
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Lock wait before a query fails with SQLITE_BUSY. Fail-fast: a transient WAL
|
|
21
|
+
* checkpoint lock should surface as an error row (and a `sql.busy` debug line)
|
|
22
|
+
* instead of blocking the whole TUI for seconds.
|
|
23
|
+
*/
|
|
24
|
+
const BUSY_TIMEOUT_MS = 100
|
|
25
|
+
|
|
26
|
+
/** One-line query label for the profile log; computed lazily (profiling off = free). */
|
|
27
|
+
function sqlLabel(sql: string): string {
|
|
28
|
+
return sql.replace(/\s+/g, " ").trim().slice(0, 80)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** True when a thrown error is a SQLite lock/busy failure (SQLITE_BUSY). */
|
|
32
|
+
export function isBusyError(e: unknown): boolean {
|
|
33
|
+
return e instanceof Error ? /locked|busy/i.test(e.message) : false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Run a query, logging a `sql.busy` debug line when the DB is locked. The
|
|
38
|
+
* error still propagates so `withDbRead` can retry once and then soft-fail.
|
|
39
|
+
*/
|
|
40
|
+
function guardQuery<T>(sql: string, fn: () => T): T {
|
|
41
|
+
try {
|
|
42
|
+
return fn()
|
|
43
|
+
} catch (e) {
|
|
44
|
+
if (isBusyError(e)) dbg("sql.busy", "db locked", { q: sqlLabel(sql) })
|
|
45
|
+
throw e
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function wrapBun(db: {
|
|
50
|
+
query: (sql: string) => { all: (...p: unknown[]) => SqlRow[]; get: (...p: unknown[]) => SqlRow | null }
|
|
51
|
+
exec: (sql: string) => void
|
|
52
|
+
close: () => void
|
|
53
|
+
}): SqlDb {
|
|
54
|
+
try {
|
|
55
|
+
db.exec("PRAGMA query_only = ON")
|
|
56
|
+
db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`)
|
|
57
|
+
} catch {
|
|
58
|
+
// older / restricted
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
all: <T extends SqlRow>(sql: string, ...params: unknown[]) =>
|
|
62
|
+
profile("sql", () => guardQuery(sql, () => db.query(sql).all(...params) as T[]), () => ({ q: sqlLabel(sql) })),
|
|
63
|
+
get: <T extends SqlRow>(sql: string, ...params: unknown[]) =>
|
|
64
|
+
profile("sql", () => guardQuery(sql, () => (db.query(sql).get(...params) as T | null) ?? null), () => ({ q: sqlLabel(sql) })),
|
|
65
|
+
close: () => {
|
|
66
|
+
try {
|
|
67
|
+
db.close()
|
|
68
|
+
} catch {
|
|
69
|
+
// ignore
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function wrapNodeSync(db: {
|
|
76
|
+
prepare: (sql: string) => {
|
|
77
|
+
all: (...p: unknown[]) => SqlRow[]
|
|
78
|
+
get: (...p: unknown[]) => SqlRow | undefined
|
|
79
|
+
}
|
|
80
|
+
exec: (sql: string) => void
|
|
81
|
+
close: () => void
|
|
82
|
+
}): SqlDb {
|
|
83
|
+
try {
|
|
84
|
+
db.exec("PRAGMA query_only = ON")
|
|
85
|
+
db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`)
|
|
86
|
+
} catch {
|
|
87
|
+
// ignore
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
all: <T extends SqlRow>(sql: string, ...params: unknown[]) =>
|
|
91
|
+
profile("sql", () => guardQuery(sql, () => db.prepare(sql).all(...params) as T[]), () => ({ q: sqlLabel(sql) })),
|
|
92
|
+
get: <T extends SqlRow>(sql: string, ...params: unknown[]) =>
|
|
93
|
+
profile("sql", () => guardQuery(sql, () => (db.prepare(sql).get(...params) as T | undefined) ?? null), () => ({ q: sqlLabel(sql) })),
|
|
94
|
+
close: () => {
|
|
95
|
+
try {
|
|
96
|
+
db.close()
|
|
97
|
+
} catch {
|
|
98
|
+
// ignore
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let hold: { path: string; db: SqlDb } | null = null
|
|
105
|
+
|
|
106
|
+
function openFresh(dbPath: string): SqlDb | null {
|
|
107
|
+
if (!dbPath || !fs.existsSync(dbPath)) return null
|
|
108
|
+
try {
|
|
109
|
+
if (typeof process.versions.bun === "string") {
|
|
110
|
+
const { Database } = require("bun:sqlite") as {
|
|
111
|
+
Database: new (
|
|
112
|
+
path: string,
|
|
113
|
+
opts?: { readonly?: boolean; create?: boolean },
|
|
114
|
+
) => Parameters<typeof wrapBun>[0]
|
|
115
|
+
}
|
|
116
|
+
return wrapBun(new Database(dbPath, { readonly: true, create: false }))
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// try node next
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const mod = require("node:sqlite") as {
|
|
123
|
+
DatabaseSync: new (
|
|
124
|
+
path: string,
|
|
125
|
+
opts?: { readOnly?: boolean },
|
|
126
|
+
) => Parameters<typeof wrapNodeSync>[0]
|
|
127
|
+
}
|
|
128
|
+
if (!mod?.DatabaseSync) return null
|
|
129
|
+
return wrapNodeSync(new mod.DatabaseSync(dbPath, { readOnly: true }))
|
|
130
|
+
} catch {
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** De-duplicated, non-empty string ids for `IN (...)` queries. */
|
|
136
|
+
export function uniqueIds(ids: readonly string[]): string[] {
|
|
137
|
+
return [...new Set(ids.filter((id) => typeof id === "string" && id.length > 0))]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Drop the cached handle so the next open is a real reopen. */
|
|
141
|
+
export function resetReadonlyDb(): void {
|
|
142
|
+
if (!hold) return
|
|
143
|
+
try {
|
|
144
|
+
hold.db.close()
|
|
145
|
+
} catch {
|
|
146
|
+
// ignore
|
|
147
|
+
}
|
|
148
|
+
hold = null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Retry a read after dropping a stale handle. */
|
|
152
|
+
export function withDbRead<T>(run: () => T, fallback: (err: unknown) => T): T {
|
|
153
|
+
try {
|
|
154
|
+
return run()
|
|
155
|
+
} catch (e) {
|
|
156
|
+
resetReadonlyDb()
|
|
157
|
+
try {
|
|
158
|
+
return run()
|
|
159
|
+
} catch {
|
|
160
|
+
return fallback(e)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Open opencode.db readonly. Reuses one handle per path. Never throws — returns null on failure. */
|
|
166
|
+
export function openReadonlyDb(dbPath: string): SqlDb | null {
|
|
167
|
+
if (!dbPath) return null
|
|
168
|
+
if (hold?.path === dbPath) return hold.db
|
|
169
|
+
resetReadonlyDb()
|
|
170
|
+
const db = openFresh(dbPath)
|
|
171
|
+
if (!db) return null
|
|
172
|
+
hold = { path: dbPath, db }
|
|
173
|
+
pushScreenLine("db open")
|
|
174
|
+
return db
|
|
175
|
+
}
|
|
@@ -0,0 +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
|
+
}
|
|
@@ -0,0 +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
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display-width helpers — columns, not code units.
|
|
3
|
+
*
|
|
4
|
+
* OpenTUI lays text out by wcwidth columns (a wide char is 2 columns, a
|
|
5
|
+
* combining mark is 0), but `String.length` counts UTF-16 code units. Every
|
|
6
|
+
* row-budget calculation in this plugin used `.length`, so a row with a wide
|
|
7
|
+
* character (emoji, CJK) measured "fits" while actually overflowing and
|
|
8
|
+
* wrapping to a second line. This module is the single place that turns text
|
|
9
|
+
* into columns and clips it grapheme-safely, so budgets are real columns.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Inclusive [start, end] code-point ranges of width-0 characters. */
|
|
13
|
+
const ZERO: readonly (readonly [number, number])[] = [
|
|
14
|
+
[0x0300, 0x036f], [0x0483, 0x0489], [0x0591, 0x05bd], [0x05bf, 0x05bf],
|
|
15
|
+
[0x05c1, 0x05c2], [0x05c4, 0x05c5], [0x05c7, 0x05c7], [0x0610, 0x061a],
|
|
16
|
+
[0x064b, 0x065f], [0x0670, 0x0670], [0x06d6, 0x06dc], [0x06df, 0x06e4],
|
|
17
|
+
[0x06e7, 0x06e8], [0x06ea, 0x06ed], [0x0711, 0x0711], [0x0730, 0x074a],
|
|
18
|
+
[0x07a6, 0x07b0], [0x07eb, 0x07f3], [0x0816, 0x0819], [0x081b, 0x0823],
|
|
19
|
+
[0x0825, 0x0827], [0x0829, 0x082d], [0x0859, 0x085b], [0x08d3, 0x0902],
|
|
20
|
+
[0x093a, 0x093a], [0x093c, 0x093c], [0x0941, 0x0948], [0x094d, 0x094d],
|
|
21
|
+
[0x0951, 0x0957], [0x0962, 0x0963], [0x0981, 0x0981], [0x09bc, 0x09bc],
|
|
22
|
+
[0x09c1, 0x09c4], [0x09cd, 0x09cd], [0x09e2, 0x09e3], [0x0a01, 0x0a02],
|
|
23
|
+
[0x0a3c, 0x0a3c], [0x0a41, 0x0a42], [0x0a47, 0x0a48], [0x0a4b, 0x0a4d],
|
|
24
|
+
[0x0a51, 0x0a51], [0x0a70, 0x0a71], [0x0a75, 0x0a75], [0x0a81, 0x0a82],
|
|
25
|
+
[0x0abc, 0x0abc], [0x0ac1, 0x0ac5], [0x0ac7, 0x0ac8], [0x0acd, 0x0acd],
|
|
26
|
+
[0x0ae2, 0x0ae3], [0x0afa, 0x0aff], [0x0b01, 0x0b01], [0x0b3c, 0x0b3c],
|
|
27
|
+
[0x0b3f, 0x0b3f], [0x0b41, 0x0b44], [0x0b4d, 0x0b4d], [0x0b56, 0x0b56],
|
|
28
|
+
[0x0b62, 0x0b63], [0x0b82, 0x0b82], [0x0bc0, 0x0bc0], [0x0bcd, 0x0bcd],
|
|
29
|
+
[0x0c00, 0x0c00], [0x0c04, 0x0c04], [0x0c3e, 0x0c40], [0x0c46, 0x0c48],
|
|
30
|
+
[0x0c4a, 0x0c4d], [0x0c55, 0x0c56], [0x0c62, 0x0c63], [0x0c81, 0x0c81],
|
|
31
|
+
[0x0cbc, 0x0cbc], [0x0cbf, 0x0cbf], [0x0cc6, 0x0cc6], [0x0ccc, 0x0ccd],
|
|
32
|
+
[0x0ce2, 0x0ce3], [0x0d00, 0x0d01], [0x0d3b, 0x0d3c], [0x0d41, 0x0d44],
|
|
33
|
+
[0x0d4d, 0x0d4d], [0x0d62, 0x0d63], [0x0dca, 0x0dca], [0x0dd2, 0x0dd4],
|
|
34
|
+
[0x0dd6, 0x0dd6], [0x0e31, 0x0e31], [0x0e34, 0x0e3a], [0x0e47, 0x0e4e],
|
|
35
|
+
[0x0eb1, 0x0eb1], [0x0eb4, 0x0ebc], [0x0ec8, 0x0ecd], [0x0f18, 0x0f19],
|
|
36
|
+
[0x0f35, 0x0f35], [0x0f37, 0x0f37], [0x0f39, 0x0f39], [0x0f71, 0x0f7e],
|
|
37
|
+
[0x0f80, 0x0f84], [0x0f86, 0x0f87], [0x0f8d, 0x0f97], [0x0f99, 0x0fbc],
|
|
38
|
+
[0x0fc6, 0x0fc6], [0x102d, 0x1030], [0x1032, 0x1037], [0x1039, 0x103a],
|
|
39
|
+
[0x103d, 0x103e], [0x1058, 0x1059], [0x105e, 0x1060], [0x1071, 0x1074],
|
|
40
|
+
[0x1082, 0x1082], [0x1085, 0x1086], [0x108d, 0x108d], [0x109d, 0x109d],
|
|
41
|
+
[0x135d, 0x135f], [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
|
|
42
|
+
[0x1772, 0x1773], [0x17b4, 0x17b5], [0x17b7, 0x17bd], [0x17c6, 0x17c6],
|
|
43
|
+
[0x17c9, 0x17d3], [0x17dd, 0x17dd], [0x180b, 0x180e], [0x1885, 0x1886],
|
|
44
|
+
[0x18a9, 0x18a9], [0x1920, 0x1922], [0x1927, 0x1928], [0x1932, 0x1932],
|
|
45
|
+
[0x1939, 0x193b], [0x1a17, 0x1a18], [0x1a1b, 0x1a1b], [0x1a56, 0x1a56],
|
|
46
|
+
[0x1a58, 0x1a5e], [0x1a60, 0x1a60], [0x1a62, 0x1a62], [0x1a65, 0x1a6c],
|
|
47
|
+
[0x1a73, 0x1a7c], [0x1a7f, 0x1a7f], [0x1ab0, 0x1ac0], [0x1b00, 0x1b03],
|
|
48
|
+
[0x1b34, 0x1b34], [0x1b36, 0x1b3a], [0x1b3c, 0x1b3c], [0x1b42, 0x1b42],
|
|
49
|
+
[0x1b6b, 0x1b73], [0x1b80, 0x1b81], [0x1ba2, 0x1ba5], [0x1ba8, 0x1ba9],
|
|
50
|
+
[0x1bab, 0x1bad], [0x1be6, 0x1be6], [0x1be8, 0x1be9], [0x1bed, 0x1bed],
|
|
51
|
+
[0x1bef, 0x1bf1], [0x1c2c, 0x1c33], [0x1c36, 0x1c37], [0x1cd0, 0x1cd2],
|
|
52
|
+
[0x1cd4, 0x1ce0], [0x1ce2, 0x1ce8], [0x1ced, 0x1ced], [0x1cf4, 0x1cf4],
|
|
53
|
+
[0x1cf8, 0x1cf9], [0x1dc0, 0x1df9], [0x1dfb, 0x1dff], [0x200b, 0x200f],
|
|
54
|
+
[0x202a, 0x202e], [0x2060, 0x2064], [0x2066, 0x206f], [0x20d0, 0x20f0],
|
|
55
|
+
[0x2cef, 0x2cf1], [0x2d7f, 0x2d7f], [0x2de0, 0x2dff], [0x302a, 0x302d],
|
|
56
|
+
[0x3099, 0x309a], [0xa66f, 0xa672], [0xa674, 0xa67d], [0xa69e, 0xa69f],
|
|
57
|
+
[0xa6f0, 0xa6f1], [0xa802, 0xa802], [0xa806, 0xa806], [0xa80b, 0xa80b],
|
|
58
|
+
[0xa825, 0xa826], [0xa8c4, 0xa8c5], [0xa8e0, 0xa8f1], [0xa926, 0xa92d],
|
|
59
|
+
[0xa947, 0xa951], [0xa980, 0xa982], [0xa9b3, 0xa9b3], [0xa9b6, 0xa9b9],
|
|
60
|
+
[0xa9bc, 0xa9bd], [0xa9e5, 0xa9e5], [0xaa29, 0xaa2e], [0xaa31, 0xaa32],
|
|
61
|
+
[0xaa35, 0xaa36], [0xaa43, 0xaa43], [0xaa4c, 0xaa4c], [0xaa7c, 0xaa7c],
|
|
62
|
+
[0xaab0, 0xaab0], [0xaab2, 0xaab4], [0xaab7, 0xaab8], [0xaabe, 0xaabf],
|
|
63
|
+
[0xaac1, 0xaac1], [0xaaec, 0xaaed], [0xaaf6, 0xaaf6], [0xabe5, 0xabe5],
|
|
64
|
+
[0xabe8, 0xabe8], [0xabed, 0xabed], [0xfb1e, 0xfb1e], [0xfe00, 0xfe0f],
|
|
65
|
+
[0xfe20, 0xfe2f], [0x101fd, 0x101fd], [0x102e0, 0x102e0], [0x10376, 0x1037a],
|
|
66
|
+
[0x10a01, 0x10a03], [0x10a05, 0x10a06], [0x10a0c, 0x10a0f], [0x10a38, 0x10a3a],
|
|
67
|
+
[0x10a3f, 0x10a3f], [0x10ae5, 0x10ae6], [0x11001, 0x11001], [0x11038, 0x11046],
|
|
68
|
+
[0x1107f, 0x11081], [0x110b3, 0x110b6], [0x110b9, 0x110ba], [0x11100, 0x11102],
|
|
69
|
+
[0x11127, 0x1112b], [0x1112d, 0x11134], [0x11173, 0x11173], [0x11180, 0x11181],
|
|
70
|
+
[0x111b6, 0x111be], [0x111ca, 0x111cc], [0x1122f, 0x11231], [0x11234, 0x11234],
|
|
71
|
+
[0x11236, 0x11237], [0x1123e, 0x1123e], [0x112df, 0x112df], [0x112e3, 0x112ea],
|
|
72
|
+
[0x11300, 0x11301], [0x1133c, 0x1133c], [0x11340, 0x11340], [0x11366, 0x1136c],
|
|
73
|
+
[0x11370, 0x11374], [0x11438, 0x1143f], [0x11442, 0x11444], [0x11446, 0x11446],
|
|
74
|
+
[0x114b3, 0x114b8], [0x114ba, 0x114ba], [0x114bf, 0x114c0], [0x114c2, 0x114c3],
|
|
75
|
+
[0x115b2, 0x115b5], [0x115bc, 0x115bd], [0x115bf, 0x115c0], [0x115dc, 0x115dd],
|
|
76
|
+
[0x11633, 0x1163a], [0x1163d, 0x1163d], [0x1163f, 0x11640], [0x116ab, 0x116ab],
|
|
77
|
+
[0x116ad, 0x116ad], [0x116b0, 0x116b5], [0x116b7, 0x116b7], [0x1171d, 0x1171f],
|
|
78
|
+
[0x11722, 0x11725], [0x11727, 0x1172b], [0x11c30, 0x11c36], [0x11c38, 0x11c3d],
|
|
79
|
+
[0x11c3f, 0x11c3f], [0x11c92, 0x11ca7], [0x11caa, 0x11cb0], [0x11cb2, 0x11cb3],
|
|
80
|
+
[0x11cb5, 0x11cb6], [0x11d31, 0x11d36], [0x11d3a, 0x11d3a], [0x11d3c, 0x11d3d],
|
|
81
|
+
[0x11d3f, 0x11d45], [0x11d47, 0x11d47], [0x16af0, 0x16af4], [0x16b30, 0x16b36],
|
|
82
|
+
[0x16f8f, 0x16f92], [0x1bc9d, 0x1bc9e], [0x1bca0, 0x1bca3], [0x1d165, 0x1d169],
|
|
83
|
+
[0x1d16d, 0x1d182], [0x1d185, 0x1d18b], [0x1d1aa, 0x1d1ad], [0x1d242, 0x1d244],
|
|
84
|
+
[0x1da00, 0x1da36], [0x1da3b, 0x1da6c], [0x1da75, 0x1da75], [0x1da84, 0x1da84],
|
|
85
|
+
[0x1da9b, 0x1da9f], [0x1daa1, 0x1daaf], [0x1e000, 0x1e006], [0x1e008, 0x1e018],
|
|
86
|
+
[0x1e01b, 0x1e021], [0x1e023, 0x1e024], [0x1e026, 0x1e02a], [0x1e8d0, 0x1e8d6],
|
|
87
|
+
[0x1e944, 0x1e94a], [0xe0100, 0xe01ef],
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
/** Inclusive [start, end] code-point ranges of width-2 (wide) characters. */
|
|
91
|
+
const WIDE: readonly (readonly [number, number])[] = [
|
|
92
|
+
[0x1100, 0x115f], [0x231a, 0x231b], [0x2329, 0x232a], [0x23e9, 0x23ec],
|
|
93
|
+
[0x23f0, 0x23f0], [0x23f3, 0x23f3], [0x25fd, 0x25fe], [0x2614, 0x2615],
|
|
94
|
+
[0x2648, 0x2653], [0x267f, 0x267f], [0x2693, 0x2693], [0x26a1, 0x26a1],
|
|
95
|
+
[0x26aa, 0x26ab], [0x26bd, 0x26be], [0x26c4, 0x26c5], [0x26ce, 0x26ce],
|
|
96
|
+
[0x26d4, 0x26d4], [0x26ea, 0x26ea], [0x26f2, 0x26f3], [0x26f5, 0x26f5],
|
|
97
|
+
[0x26fa, 0x26fa], [0x26fd, 0x26fd], [0x2705, 0x2705], [0x270a, 0x270b],
|
|
98
|
+
[0x2728, 0x2728], [0x274c, 0x274c], [0x274e, 0x274e], [0x2753, 0x2755],
|
|
99
|
+
[0x2757, 0x2757], [0x2795, 0x2797], [0x27b0, 0x27b0], [0x27bf, 0x27bf],
|
|
100
|
+
[0x2b1b, 0x2b1c], [0x2b50, 0x2b50], [0x2b55, 0x2b55], [0x2e80, 0x2e99],
|
|
101
|
+
[0x2e9b, 0x2ef3], [0x2f00, 0x2fd5], [0x2ff0, 0x2ffb], [0x3000, 0x303e],
|
|
102
|
+
[0x3041, 0x3096], [0x3099, 0x30ff], [0x3105, 0x312f], [0x3131, 0x318e],
|
|
103
|
+
[0x3190, 0x31e3], [0x31f0, 0x321e], [0x3220, 0x3247], [0x3250, 0x4dbf],
|
|
104
|
+
[0x4e00, 0xa48c], [0xa490, 0xa4c6], [0xa960, 0xa97c], [0xac00, 0xd7a3],
|
|
105
|
+
[0xf900, 0xfaff], [0xfe10, 0xfe19], [0xfe30, 0xfe52], [0xfe54, 0xfe66],
|
|
106
|
+
[0xfe68, 0xfe6b], [0xff00, 0xff60], [0xffe0, 0xffe6], [0x16fe0, 0x16fe4],
|
|
107
|
+
[0x17000, 0x187f7], [0x18800, 0x18cd5], [0x18d00, 0x18d08], [0x1b000, 0x1b11e],
|
|
108
|
+
[0x1b150, 0x1b152], [0x1b164, 0x1b167], [0x1b170, 0x1b2fb], [0x1f004, 0x1f004],
|
|
109
|
+
[0x1f0cf, 0x1f0cf], [0x1f18e, 0x1f18e], [0x1f191, 0x1f19a], [0x1f200, 0x1f202],
|
|
110
|
+
[0x1f210, 0x1f23b], [0x1f240, 0x1f248], [0x1f250, 0x1f251], [0x1f260, 0x1f265],
|
|
111
|
+
[0x1f300, 0x1f320], [0x1f32d, 0x1f335], [0x1f337, 0x1f37c], [0x1f37e, 0x1f393],
|
|
112
|
+
[0x1f3a0, 0x1f3ca], [0x1f3cf, 0x1f3d3], [0x1f3e0, 0x1f3f0], [0x1f3f4, 0x1f3f4],
|
|
113
|
+
[0x1f3f8, 0x1f43e], [0x1f440, 0x1f440], [0x1f442, 0x1f4fc], [0x1f4ff, 0x1f53d],
|
|
114
|
+
[0x1f54b, 0x1f54e], [0x1f550, 0x1f567], [0x1f57a, 0x1f57a], [0x1f595, 0x1f596],
|
|
115
|
+
[0x1f5a4, 0x1f5a4], [0x1f5fb, 0x1f64f], [0x1f680, 0x1f6c5], [0x1f6cc, 0x1f6cc],
|
|
116
|
+
[0x1f6d0, 0x1f6d2], [0x1f6d5, 0x1f6d7], [0x1f6eb, 0x1f6ec], [0x1f6f4, 0x1f6fc],
|
|
117
|
+
[0x1f7e0, 0x1f7eb], [0x1f90c, 0x1f93a], [0x1f93c, 0x1f945], [0x1f947, 0x1f978],
|
|
118
|
+
[0x1f97a, 0x1f9cb], [0x1f9cd, 0x1f9ff], [0x1fa70, 0x1fa74], [0x1fa78, 0x1fa7a],
|
|
119
|
+
[0x1fa80, 0x1fa86], [0x1fa90, 0x1faa8], [0x1fab0, 0x1fab6], [0x1fac0, 0x1fac2],
|
|
120
|
+
[0x1fad0, 0x1fad6], [0x20000, 0x2fffd], [0x30000, 0x3fffd],
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
function inRanges(cp: number, table: readonly (readonly [number, number])[]): boolean {
|
|
124
|
+
let lo = 0
|
|
125
|
+
let hi = table.length - 1
|
|
126
|
+
while (lo <= hi) {
|
|
127
|
+
const mid = (lo + hi) >> 1
|
|
128
|
+
const [a, b] = table[mid]!
|
|
129
|
+
if (cp < a) hi = mid - 1
|
|
130
|
+
else if (cp > b) lo = mid + 1
|
|
131
|
+
else return true
|
|
132
|
+
}
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** wcwidth of one code point: 0 combining/control, 2 wide, else 1. */
|
|
137
|
+
export function codepointWidth(cp: number): number {
|
|
138
|
+
if (cp === 0) return 0
|
|
139
|
+
if (cp < 32 || (cp >= 0x7f && cp < 0xa0)) return 0
|
|
140
|
+
if (inRanges(cp, ZERO)) return 0
|
|
141
|
+
if (inRanges(cp, WIDE)) return 2
|
|
142
|
+
return 1
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Display width in columns (wcwidth) of a string. */
|
|
146
|
+
export function strWidth(s: string): number {
|
|
147
|
+
let w = 0
|
|
148
|
+
for (const ch of s) w += codepointWidth(ch.codePointAt(0)!)
|
|
149
|
+
return w
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Split into grapheme clusters; falls back to code points when Segmenter is absent. */
|
|
153
|
+
function graphemes(s: string): string[] {
|
|
154
|
+
const Seg = (Intl as unknown as {
|
|
155
|
+
Segmenter?: new (l?: string, o?: { granularity: string }) => { segment: (t: string) => Iterable<{ segment: string }> }
|
|
156
|
+
}).Segmenter
|
|
157
|
+
if (typeof Seg === "function") {
|
|
158
|
+
return Array.from(new Seg(undefined, { granularity: "grapheme" }).segment(s), (x) => x.segment)
|
|
159
|
+
}
|
|
160
|
+
return Array.from(s)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Longest prefix of `s` whose column width is at most `cols`. */
|
|
164
|
+
export function takeCols(s: string, cols: number): string {
|
|
165
|
+
if (cols <= 0) return ""
|
|
166
|
+
let width = 0
|
|
167
|
+
let out = ""
|
|
168
|
+
for (const g of graphemes(s)) {
|
|
169
|
+
const gw = strWidth(g)
|
|
170
|
+
if (width + gw > cols) break
|
|
171
|
+
width += gw
|
|
172
|
+
out += g
|
|
173
|
+
}
|
|
174
|
+
return out
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Longest suffix of `s` whose column width is at most `cols`. */
|
|
178
|
+
export function takeLastCols(s: string, cols: number): string {
|
|
179
|
+
if (cols <= 0) return ""
|
|
180
|
+
const gs = graphemes(s)
|
|
181
|
+
let width = 0
|
|
182
|
+
const picked: string[] = []
|
|
183
|
+
for (let i = gs.length - 1; i >= 0; i--) {
|
|
184
|
+
const g = gs[i]!
|
|
185
|
+
const gw = strWidth(g)
|
|
186
|
+
if (width + gw > cols) break
|
|
187
|
+
width += gw
|
|
188
|
+
picked.unshift(g)
|
|
189
|
+
}
|
|
190
|
+
return picked.join("")
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const ELLIPSIS = "…"
|
|
194
|
+
|
|
195
|
+
/** Clip to at most `max` columns with a trailing ellipsis; collapses whitespace. */
|
|
196
|
+
export function clipWidth(s: string, max: number): string {
|
|
197
|
+
const t = s.replace(/\s+/g, " ").trim()
|
|
198
|
+
if (max <= 0) return ""
|
|
199
|
+
if (strWidth(t) <= max) return t
|
|
200
|
+
if (max === 1) return ELLIPSIS
|
|
201
|
+
return `${takeCols(t, max - strWidth(ELLIPSIS))}${ELLIPSIS}`
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Clip to at most `max` columns with a middle ellipsis; collapses whitespace. */
|
|
205
|
+
export function clipMiddleWidth(s: string, max: number): string {
|
|
206
|
+
const t = s.replace(/\s+/g, " ").trim()
|
|
207
|
+
if (max <= 0) return ""
|
|
208
|
+
if (strWidth(t) <= max) return t
|
|
209
|
+
if (max <= 2) return takeCols(t, max)
|
|
210
|
+
// Suffix-bias the middle split, but never wider than the post-ellipsis budget.
|
|
211
|
+
const budget = max - strWidth(ELLIPSIS)
|
|
212
|
+
const tail = Math.min(budget, Math.max(3, Math.floor(budget / 2)))
|
|
213
|
+
const head = budget - tail
|
|
214
|
+
return `${takeCols(t, head)}${ELLIPSIS}${takeLastCols(t, tail)}`
|
|
215
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.omo.constants
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the OMO string literals: plan pending statuses,
|
|
5
|
+
* boulder statuses, run-continuation background-task states, review-lifecycle
|
|
6
|
+
* statuses, and review lane verdicts.
|
|
7
|
+
*/
|
|
8
|
+
export * from "./pware.oc.omo.constants.planStatus.js"
|
|
9
|
+
export * from "./pware.oc.omo.constants.boulderStatus.js"
|
|
10
|
+
export * from "./pware.oc.omo.constants.backgroundTask.js"
|
|
11
|
+
export * from "./pware.oc.omo.constants.reviewStatus.js"
|
|
12
|
+
export * from "./pware.oc.omo.constants.verdict.js"
|
|
13
|
+
export * from "./pware.oc.omo.constants.docKind.js"
|
|
14
|
+
export * from "./pware.oc.omo.constants.startWork.js"
|
|
15
|
+
export * from "./pware.oc.omo.constants.eventName.js"
|