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.
- package/.oesignore +17 -17
- package/CHANGELOG.md +96 -95
- package/LICENSE +21 -21
- package/README.md +150 -235
- package/oes.json +13 -11
- package/package.json +100 -100
- package/scripts/install-git-hooks.mjs +29 -29
- package/src/pware.oc.core/constants/index.ts +18 -18
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -29
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +15 -13
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -241
- package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -55
- package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -65
- package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -58
- package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -63
- package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -34
- package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -74
- package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -69
- package/src/pware.oc.core/git/index.ts +7 -7
- package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -237
- package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -87
- package/src/pware.oc.core/index.ts +19 -18
- package/src/pware.oc.core/pware.oc.core.bus.ts +42 -42
- package/src/pware.oc.core/pware.oc.core.cache.ts +48 -48
- package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -97
- package/src/pware.oc.core/pware.oc.core.debug.ts +280 -280
- package/src/pware.oc.core/pware.oc.core.events.ts +79 -79
- package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -135
- package/src/pware.oc.core/pware.oc.core.layout.ts +234 -234
- package/src/pware.oc.core/pware.oc.core.oes.ts +99 -89
- package/src/pware.oc.core/pware.oc.core.paths.ts +240 -240
- package/src/pware.oc.core/pware.oc.core.preview.ts +74 -74
- package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -726
- package/src/pware.oc.core/pware.oc.core.runtimeConfig.ts +41 -0
- package/src/pware.oc.core/pware.oc.core.sqlite.ts +164 -175
- package/src/pware.oc.core/pware.oc.core.sqliteGateway.ts +89 -0
- package/src/pware.oc.core/pware.oc.core.status.ts +214 -214
- package/src/pware.oc.core/pware.oc.core.timing.ts +63 -63
- package/src/pware.oc.core/pware.oc.core.width.ts +215 -215
- package/src/pware.oc.omo/constants/index.ts +15 -15
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -18
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -49
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -29
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -11
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -60
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -49
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -25
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -38
- package/src/pware.oc.omo/index.ts +7 -7
- package/src/pware.oc.omo/resolver/index.ts +19 -19
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -179
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -116
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -34
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -503
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -40
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -269
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -155
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +363 -361
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -11
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -14
- package/src/pware.oc.opencode/constants/index.ts +10 -10
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -15
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -18
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -45
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -58
- package/src/pware.oc.opencode/index.ts +8 -8
- package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -182
- package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -399
- package/src/pware.oc.opencode/resolver/index.ts +223 -170
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +150 -162
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +149 -185
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +248 -211
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +22 -26
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +224 -236
- package/src/pware.oc.perf/index.ts +14 -14
- package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -15
- package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -284
- package/src/pware.oc.perf/pware.oc.perf.reader.ts +962 -927
- package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -88
- package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -118
- package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -79
- package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -71
- package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -214
- package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -179
- package/src/pware.oc.perf/pware.oc.perf.view.tsx +463 -462
- package/src/pware.oc.runtime/index.ts +13 -13
- package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -142
- package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +87 -79
- package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +282 -253
- package/src/pware.oc.runtime/pware.oc.runtime.omoRead.ts +37 -0
- package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +83 -95
- package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +130 -129
- package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -105
- package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +44 -43
- package/src/pware.oc.runtime/resolver/index.ts +268 -224
- package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -146
- package/src/pware.oc.ui/index.ts +12 -12
- package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +381 -339
- package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -144
- package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -111
- package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -82
- package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -843
- package/src/pware.oc.ui/pware.oc.ui.sections.tsx +552 -526
- package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1718 -1629
- package/src/pware.oc.ui.tsx +98 -98
|
@@ -1,234 +1,234 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vertical row budget for the sidebar. Pure — no OpenTUI, unit tested directly.
|
|
3
|
-
* Same idea as `packChips` in pulse.ts, one axis over: under pressure the worst
|
|
4
|
-
* rank gives up rows first, and nothing drops below its `min` until every
|
|
5
|
-
* section has already reached it.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Rows OpenCode's own TUI takes around the `sidebar_content` slot. Measured
|
|
10
|
-
* from the opencode TUI source (`packages/tui/src/routes/session/sidebar.tsx`,
|
|
11
|
-
* stable across v1.18.27 = HEAD): the sidebar column has no border — its frame
|
|
12
|
-
* is `paddingTop` 1 + a fixed footer (`sidebar_footer`, outside the scrollbox:
|
|
13
|
-
* 1 spacer + path row + 1 gap + version row) + `paddingBottom` 1.
|
|
14
|
-
*/
|
|
15
|
-
const HOST_SIDEBAR_CHROME = 6
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Rows the host's `sidebar_title` fallback block (same scrollbox, above the
|
|
19
|
-
* content slot) takes before the plugin's first content row: a typical session
|
|
20
|
-
* shows a bold title + workspace (2 rows) plus the 1-row `gap` separator, and
|
|
21
|
-
* a wrapping title or the optional channel/share lines add more. Kept at 4 so
|
|
22
|
-
* `PANEL_CHROME` still covers the old flat-10 headroom.
|
|
23
|
-
*/
|
|
24
|
-
const HOST_SIDEBAR_TITLE_RESERVE = 4
|
|
25
|
-
|
|
26
|
-
/** Fixed chrome the sidebar slot's own host takes above the content rows. */
|
|
27
|
-
const PANEL_CHROME = HOST_SIDEBAR_CHROME + HOST_SIDEBAR_TITLE_RESERVE
|
|
28
|
-
const PANEL_MIN_ROWS = 8
|
|
29
|
-
|
|
30
|
-
/** Shared right rail for context actions (`switch new`, `view all`, …). */
|
|
31
|
-
export const CONTEXT_ACTION_COL_WIDTH = 10
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Row width budget before the root box reports its measured layout width.
|
|
35
|
-
* `lineMax` used to be an oes.json option; it is now measured, and this is the
|
|
36
|
-
* pre-layout fallback (matches the old default).
|
|
37
|
-
*/
|
|
38
|
-
export const ROW_LINE_FALLBACK = 31
|
|
39
|
-
|
|
40
|
-
/** Columns subtracted from the measured root width for the scrollbar reserve. */
|
|
41
|
-
export const ROW_LINE_RESERVE = 1
|
|
42
|
-
|
|
43
|
-
/** Realtime charts link on the OES row (`C` → fullscreen dialog). */
|
|
44
|
-
export const RT_ACTION_COL_WIDTH = 1
|
|
45
|
-
|
|
46
|
-
/** Compact sparkline body rows — sidebar inline chart removed; kept for docs/tests. */
|
|
47
|
-
export const RT_CHART_ROWS = 3
|
|
48
|
-
|
|
49
|
-
/** Per-series sparkline body rows in the realtime fullscreen dialog. */
|
|
50
|
-
export const RT_DIALOG_CHART_ROWS = 8
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Host `api.ui.Dialog` inner box widths (`packages/opencode/.../dialog.tsx`).
|
|
54
|
-
* `maxWidth` is `terminal − 2`. `xlarge` is 116 on hosts that implement it
|
|
55
|
-
* (#44754); older builds only switch `large` (80) vs else (60).
|
|
56
|
-
*/
|
|
57
|
-
export const HOST_DIALOG_OUTER_WIDTH = { medium: 60, large: 80, xlarge: 116 } as const
|
|
58
|
-
export type HostDialogSize = keyof typeof HOST_DIALOG_OUTER_WIDTH
|
|
59
|
-
|
|
60
|
-
/** `maxWidth={dimensions().width - 2}` on the host inner box. */
|
|
61
|
-
const HOST_DIALOG_MAX_INSET = 2
|
|
62
|
-
/** `DialogPad` left + right, matching the host dialog bodies (`paddingLeft/Right 2`). */
|
|
63
|
-
const DIALOG_PAD_X = 4
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Columns inside the padded dialog body. Subtracts the host box cap, the
|
|
67
|
-
* `maxWidth` inset, and DialogPad — this is the line budget a chart may use,
|
|
68
|
-
* including the asciiTrend min/max labels on the left of each row.
|
|
69
|
-
*/
|
|
70
|
-
export function dialogInnerWidth(termWidth: number, size: HostDialogSize): number {
|
|
71
|
-
const w = Number.isFinite(termWidth) && termWidth > 0 ? Math.floor(termWidth) : 80
|
|
72
|
-
const outer = Math.min(HOST_DIALOG_OUTER_WIDTH[size], Math.max(1, w - HOST_DIALOG_MAX_INSET))
|
|
73
|
-
return Math.max(8, outer - DIALOG_PAD_X)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* One right-aligned action line padded to {@link CONTEXT_ACTION_COL_WIDTH}.
|
|
78
|
-
* Long labels keep their trailing characters so `view all` still fits the rail.
|
|
79
|
-
*/
|
|
80
|
-
export function contextActionsLine(labels: readonly string[]): string {
|
|
81
|
-
const text = labels.join(" ")
|
|
82
|
-
if (text.length >= CONTEXT_ACTION_COL_WIDTH) return text.slice(-CONTEXT_ACTION_COL_WIDTH)
|
|
83
|
-
return text.padStart(CONTEXT_ACTION_COL_WIDTH)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Usable rows in the sidebar slot for a terminal that tall. */
|
|
87
|
-
export function panelRows(termHeight: number): number {
|
|
88
|
-
const h = Number.isFinite(termHeight) && termHeight > 0 ? termHeight : 24
|
|
89
|
-
return Math.max(PANEL_MIN_ROWS, Math.floor(h) - PANEL_CHROME)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Rows a section keeps before it is worth folding instead. */
|
|
93
|
-
export const ROW_MIN = {
|
|
94
|
-
delegates: 2,
|
|
95
|
-
drafts: 2,
|
|
96
|
-
files: 3,
|
|
97
|
-
mywork: 2,
|
|
98
|
-
tools: 3,
|
|
99
|
-
} as const
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Higher gives up rows first. Live activity outranks history: on Current the
|
|
103
|
-
* tool feed survives longest. My work is a queue of things awaiting the user,
|
|
104
|
-
* so it ties with the live feed.
|
|
105
|
-
*/
|
|
106
|
-
export const ROW_RANK = {
|
|
107
|
-
delegates: 3,
|
|
108
|
-
drafts: 3,
|
|
109
|
-
files: 2,
|
|
110
|
-
mywork: 1,
|
|
111
|
-
tools: 1,
|
|
112
|
-
} as const
|
|
113
|
-
|
|
114
|
-
export type Section<K extends string = string> = {
|
|
115
|
-
key: K
|
|
116
|
-
/** Rows the section would like — usually the matching `oes.json` value. */
|
|
117
|
-
want: number
|
|
118
|
-
min: number
|
|
119
|
-
rank: number
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Split `budget - fixed` rows across the sections. Ties are broken by whoever
|
|
124
|
-
* currently holds the most rows, so equal ranks shrink together. A section that
|
|
125
|
-
* hits `min` while the budget is still short is handed `0` — the caller folds
|
|
126
|
-
* it rather than rendering a stub.
|
|
127
|
-
*/
|
|
128
|
-
export function packSections<K extends string>(
|
|
129
|
-
budget: number,
|
|
130
|
-
fixed: number,
|
|
131
|
-
sections: readonly Section<K>[],
|
|
132
|
-
): Record<K, number> {
|
|
133
|
-
const live = sections.map((s) => {
|
|
134
|
-
const min = Math.max(0, Math.round(s.min))
|
|
135
|
-
return { key: s.key, min, rank: s.rank, rows: Math.max(min, Math.max(0, Math.round(s.want))) }
|
|
136
|
-
})
|
|
137
|
-
const available = Math.max(0, Math.round(budget) - Math.round(fixed))
|
|
138
|
-
const total = () => live.reduce((sum, s) => sum + s.rows, 0)
|
|
139
|
-
|
|
140
|
-
const worstAbove = (floor: (s: (typeof live)[number]) => number): number => {
|
|
141
|
-
let worst = -1
|
|
142
|
-
for (let i = 0; i < live.length; i += 1) {
|
|
143
|
-
const s = live[i]!
|
|
144
|
-
if (s.rows <= floor(s)) continue
|
|
145
|
-
const best = worst < 0 ? null : live[worst]!
|
|
146
|
-
if (!best || s.rank > best.rank || (s.rank === best.rank && s.rows > best.rows)) worst = i
|
|
147
|
-
}
|
|
148
|
-
return worst
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
while (total() > available) {
|
|
152
|
-
const i = worstAbove((s) => s.min)
|
|
153
|
-
if (i < 0) break
|
|
154
|
-
live[i]!.rows -= 1
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Everyone is at their minimum and it still does not fit: fold, worst first.
|
|
158
|
-
while (total() > available) {
|
|
159
|
-
const i = worstAbove(() => 0)
|
|
160
|
-
if (i < 0) break
|
|
161
|
-
live[i]!.rows = 0
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const out = {} as Record<K, number>
|
|
165
|
-
for (const s of live) out[s.key] = s.rows
|
|
166
|
-
return out
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Look up a packed section. A missing plan (Solid memo still computing,
|
|
171
|
-
* previous compute threw, owner disposed) or a missing / non-finite key
|
|
172
|
-
* returns `fallback` — the sidebar slot must not die on `plan[key]`.
|
|
173
|
-
*/
|
|
174
|
-
export function rowsForPlan(
|
|
175
|
-
plan: Readonly<Record<string, number>> | null | undefined,
|
|
176
|
-
key: string,
|
|
177
|
-
fallback: number,
|
|
178
|
-
): number {
|
|
179
|
-
if (!plan || typeof plan !== "object") return fallback
|
|
180
|
-
const v = plan[key]
|
|
181
|
-
return typeof v === "number" && Number.isFinite(v) ? v : fallback
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Show `shown` rows and report how many a "more" control can still reveal.
|
|
186
|
-
* The expander is a separate clickable line (`RowList`) the caller renders
|
|
187
|
-
* below the list — no row is spent on the note.
|
|
188
|
-
*/
|
|
189
|
-
export function sliceShown<T>(
|
|
190
|
-
rows: readonly T[],
|
|
191
|
-
shown: number,
|
|
192
|
-
): { rows: T[]; hidden: number } {
|
|
193
|
-
const s = Math.max(0, Math.round(shown))
|
|
194
|
-
const kept = rows.slice(0, s)
|
|
195
|
-
return { rows: kept, hidden: Math.max(0, rows.length - kept.length) }
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Whether the "… +N more" / "… less" line is drawn at all. The expanded toggle
|
|
200
|
-
* keeps its "… less" line even when nothing is hidden; otherwise the line
|
|
201
|
-
* vanishes the moment there is nothing left to reveal.
|
|
202
|
-
*/
|
|
203
|
-
export function moreRevealVisible(hidden: number, expanded?: boolean): boolean {
|
|
204
|
-
return expanded === true || hidden > 0
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Clamp a scroll offset into the window a `visible`-row view can show over
|
|
209
|
-
* `total` rows: the offset never goes below 0 or past the last full window.
|
|
210
|
-
*/
|
|
211
|
-
export function clampScrollOffset(total: number, visible: number, offset: number): number {
|
|
212
|
-
const t = Math.max(0, Math.round(total))
|
|
213
|
-
const v = Math.max(1, Math.round(visible))
|
|
214
|
-
const max = Math.max(0, t - v)
|
|
215
|
-
const o = Math.max(0, Math.round(offset))
|
|
216
|
-
return Math.min(o, max)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Move a scroll offset by one wheel `step`. Newest-first lists count the offset
|
|
221
|
-
* from the newest row, so "down" goes toward older rows and "up" back to the
|
|
222
|
-
* newest. The result stays inside the same clamped window as `clampScrollOffset`.
|
|
223
|
-
*/
|
|
224
|
-
export function scrollByStep(
|
|
225
|
-
total: number,
|
|
226
|
-
visible: number,
|
|
227
|
-
offset: number,
|
|
228
|
-
direction: "up" | "down",
|
|
229
|
-
step: number,
|
|
230
|
-
): number {
|
|
231
|
-
const s = Math.max(1, Math.round(step))
|
|
232
|
-
const next = direction === "down" ? offset + s : offset - s
|
|
233
|
-
return clampScrollOffset(total, visible, next)
|
|
234
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Vertical row budget for the sidebar. Pure — no OpenTUI, unit tested directly.
|
|
3
|
+
* Same idea as `packChips` in pulse.ts, one axis over: under pressure the worst
|
|
4
|
+
* rank gives up rows first, and nothing drops below its `min` until every
|
|
5
|
+
* section has already reached it.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Rows OpenCode's own TUI takes around the `sidebar_content` slot. Measured
|
|
10
|
+
* from the opencode TUI source (`packages/tui/src/routes/session/sidebar.tsx`,
|
|
11
|
+
* stable across v1.18.27 = HEAD): the sidebar column has no border — its frame
|
|
12
|
+
* is `paddingTop` 1 + a fixed footer (`sidebar_footer`, outside the scrollbox:
|
|
13
|
+
* 1 spacer + path row + 1 gap + version row) + `paddingBottom` 1.
|
|
14
|
+
*/
|
|
15
|
+
const HOST_SIDEBAR_CHROME = 6
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Rows the host's `sidebar_title` fallback block (same scrollbox, above the
|
|
19
|
+
* content slot) takes before the plugin's first content row: a typical session
|
|
20
|
+
* shows a bold title + workspace (2 rows) plus the 1-row `gap` separator, and
|
|
21
|
+
* a wrapping title or the optional channel/share lines add more. Kept at 4 so
|
|
22
|
+
* `PANEL_CHROME` still covers the old flat-10 headroom.
|
|
23
|
+
*/
|
|
24
|
+
const HOST_SIDEBAR_TITLE_RESERVE = 4
|
|
25
|
+
|
|
26
|
+
/** Fixed chrome the sidebar slot's own host takes above the content rows. */
|
|
27
|
+
const PANEL_CHROME = HOST_SIDEBAR_CHROME + HOST_SIDEBAR_TITLE_RESERVE
|
|
28
|
+
const PANEL_MIN_ROWS = 8
|
|
29
|
+
|
|
30
|
+
/** Shared right rail for context actions (`switch new`, `view all`, …). */
|
|
31
|
+
export const CONTEXT_ACTION_COL_WIDTH = 10
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Row width budget before the root box reports its measured layout width.
|
|
35
|
+
* `lineMax` used to be an oes.json option; it is now measured, and this is the
|
|
36
|
+
* pre-layout fallback (matches the old default).
|
|
37
|
+
*/
|
|
38
|
+
export const ROW_LINE_FALLBACK = 31
|
|
39
|
+
|
|
40
|
+
/** Columns subtracted from the measured root width for the scrollbar reserve. */
|
|
41
|
+
export const ROW_LINE_RESERVE = 1
|
|
42
|
+
|
|
43
|
+
/** Realtime charts link on the OES row (`C` → fullscreen dialog). */
|
|
44
|
+
export const RT_ACTION_COL_WIDTH = 1
|
|
45
|
+
|
|
46
|
+
/** Compact sparkline body rows — sidebar inline chart removed; kept for docs/tests. */
|
|
47
|
+
export const RT_CHART_ROWS = 3
|
|
48
|
+
|
|
49
|
+
/** Per-series sparkline body rows in the realtime fullscreen dialog. */
|
|
50
|
+
export const RT_DIALOG_CHART_ROWS = 8
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Host `api.ui.Dialog` inner box widths (`packages/opencode/.../dialog.tsx`).
|
|
54
|
+
* `maxWidth` is `terminal − 2`. `xlarge` is 116 on hosts that implement it
|
|
55
|
+
* (#44754); older builds only switch `large` (80) vs else (60).
|
|
56
|
+
*/
|
|
57
|
+
export const HOST_DIALOG_OUTER_WIDTH = { medium: 60, large: 80, xlarge: 116 } as const
|
|
58
|
+
export type HostDialogSize = keyof typeof HOST_DIALOG_OUTER_WIDTH
|
|
59
|
+
|
|
60
|
+
/** `maxWidth={dimensions().width - 2}` on the host inner box. */
|
|
61
|
+
const HOST_DIALOG_MAX_INSET = 2
|
|
62
|
+
/** `DialogPad` left + right, matching the host dialog bodies (`paddingLeft/Right 2`). */
|
|
63
|
+
const DIALOG_PAD_X = 4
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Columns inside the padded dialog body. Subtracts the host box cap, the
|
|
67
|
+
* `maxWidth` inset, and DialogPad — this is the line budget a chart may use,
|
|
68
|
+
* including the asciiTrend min/max labels on the left of each row.
|
|
69
|
+
*/
|
|
70
|
+
export function dialogInnerWidth(termWidth: number, size: HostDialogSize): number {
|
|
71
|
+
const w = Number.isFinite(termWidth) && termWidth > 0 ? Math.floor(termWidth) : 80
|
|
72
|
+
const outer = Math.min(HOST_DIALOG_OUTER_WIDTH[size], Math.max(1, w - HOST_DIALOG_MAX_INSET))
|
|
73
|
+
return Math.max(8, outer - DIALOG_PAD_X)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* One right-aligned action line padded to {@link CONTEXT_ACTION_COL_WIDTH}.
|
|
78
|
+
* Long labels keep their trailing characters so `view all` still fits the rail.
|
|
79
|
+
*/
|
|
80
|
+
export function contextActionsLine(labels: readonly string[]): string {
|
|
81
|
+
const text = labels.join(" ")
|
|
82
|
+
if (text.length >= CONTEXT_ACTION_COL_WIDTH) return text.slice(-CONTEXT_ACTION_COL_WIDTH)
|
|
83
|
+
return text.padStart(CONTEXT_ACTION_COL_WIDTH)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Usable rows in the sidebar slot for a terminal that tall. */
|
|
87
|
+
export function panelRows(termHeight: number): number {
|
|
88
|
+
const h = Number.isFinite(termHeight) && termHeight > 0 ? termHeight : 24
|
|
89
|
+
return Math.max(PANEL_MIN_ROWS, Math.floor(h) - PANEL_CHROME)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Rows a section keeps before it is worth folding instead. */
|
|
93
|
+
export const ROW_MIN = {
|
|
94
|
+
delegates: 2,
|
|
95
|
+
drafts: 2,
|
|
96
|
+
files: 3,
|
|
97
|
+
mywork: 2,
|
|
98
|
+
tools: 3,
|
|
99
|
+
} as const
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Higher gives up rows first. Live activity outranks history: on Current the
|
|
103
|
+
* tool feed survives longest. My work is a queue of things awaiting the user,
|
|
104
|
+
* so it ties with the live feed.
|
|
105
|
+
*/
|
|
106
|
+
export const ROW_RANK = {
|
|
107
|
+
delegates: 3,
|
|
108
|
+
drafts: 3,
|
|
109
|
+
files: 2,
|
|
110
|
+
mywork: 1,
|
|
111
|
+
tools: 1,
|
|
112
|
+
} as const
|
|
113
|
+
|
|
114
|
+
export type Section<K extends string = string> = {
|
|
115
|
+
key: K
|
|
116
|
+
/** Rows the section would like — usually the matching `oes.json` value. */
|
|
117
|
+
want: number
|
|
118
|
+
min: number
|
|
119
|
+
rank: number
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Split `budget - fixed` rows across the sections. Ties are broken by whoever
|
|
124
|
+
* currently holds the most rows, so equal ranks shrink together. A section that
|
|
125
|
+
* hits `min` while the budget is still short is handed `0` — the caller folds
|
|
126
|
+
* it rather than rendering a stub.
|
|
127
|
+
*/
|
|
128
|
+
export function packSections<K extends string>(
|
|
129
|
+
budget: number,
|
|
130
|
+
fixed: number,
|
|
131
|
+
sections: readonly Section<K>[],
|
|
132
|
+
): Record<K, number> {
|
|
133
|
+
const live = sections.map((s) => {
|
|
134
|
+
const min = Math.max(0, Math.round(s.min))
|
|
135
|
+
return { key: s.key, min, rank: s.rank, rows: Math.max(min, Math.max(0, Math.round(s.want))) }
|
|
136
|
+
})
|
|
137
|
+
const available = Math.max(0, Math.round(budget) - Math.round(fixed))
|
|
138
|
+
const total = () => live.reduce((sum, s) => sum + s.rows, 0)
|
|
139
|
+
|
|
140
|
+
const worstAbove = (floor: (s: (typeof live)[number]) => number): number => {
|
|
141
|
+
let worst = -1
|
|
142
|
+
for (let i = 0; i < live.length; i += 1) {
|
|
143
|
+
const s = live[i]!
|
|
144
|
+
if (s.rows <= floor(s)) continue
|
|
145
|
+
const best = worst < 0 ? null : live[worst]!
|
|
146
|
+
if (!best || s.rank > best.rank || (s.rank === best.rank && s.rows > best.rows)) worst = i
|
|
147
|
+
}
|
|
148
|
+
return worst
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
while (total() > available) {
|
|
152
|
+
const i = worstAbove((s) => s.min)
|
|
153
|
+
if (i < 0) break
|
|
154
|
+
live[i]!.rows -= 1
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Everyone is at their minimum and it still does not fit: fold, worst first.
|
|
158
|
+
while (total() > available) {
|
|
159
|
+
const i = worstAbove(() => 0)
|
|
160
|
+
if (i < 0) break
|
|
161
|
+
live[i]!.rows = 0
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const out = {} as Record<K, number>
|
|
165
|
+
for (const s of live) out[s.key] = s.rows
|
|
166
|
+
return out
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Look up a packed section. A missing plan (Solid memo still computing,
|
|
171
|
+
* previous compute threw, owner disposed) or a missing / non-finite key
|
|
172
|
+
* returns `fallback` — the sidebar slot must not die on `plan[key]`.
|
|
173
|
+
*/
|
|
174
|
+
export function rowsForPlan(
|
|
175
|
+
plan: Readonly<Record<string, number>> | null | undefined,
|
|
176
|
+
key: string,
|
|
177
|
+
fallback: number,
|
|
178
|
+
): number {
|
|
179
|
+
if (!plan || typeof plan !== "object") return fallback
|
|
180
|
+
const v = plan[key]
|
|
181
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Show `shown` rows and report how many a "more" control can still reveal.
|
|
186
|
+
* The expander is a separate clickable line (`RowList`) the caller renders
|
|
187
|
+
* below the list — no row is spent on the note.
|
|
188
|
+
*/
|
|
189
|
+
export function sliceShown<T>(
|
|
190
|
+
rows: readonly T[],
|
|
191
|
+
shown: number,
|
|
192
|
+
): { rows: T[]; hidden: number } {
|
|
193
|
+
const s = Math.max(0, Math.round(shown))
|
|
194
|
+
const kept = rows.slice(0, s)
|
|
195
|
+
return { rows: kept, hidden: Math.max(0, rows.length - kept.length) }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Whether the "… +N more" / "… less" line is drawn at all. The expanded toggle
|
|
200
|
+
* keeps its "… less" line even when nothing is hidden; otherwise the line
|
|
201
|
+
* vanishes the moment there is nothing left to reveal.
|
|
202
|
+
*/
|
|
203
|
+
export function moreRevealVisible(hidden: number, expanded?: boolean): boolean {
|
|
204
|
+
return expanded === true || hidden > 0
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Clamp a scroll offset into the window a `visible`-row view can show over
|
|
209
|
+
* `total` rows: the offset never goes below 0 or past the last full window.
|
|
210
|
+
*/
|
|
211
|
+
export function clampScrollOffset(total: number, visible: number, offset: number): number {
|
|
212
|
+
const t = Math.max(0, Math.round(total))
|
|
213
|
+
const v = Math.max(1, Math.round(visible))
|
|
214
|
+
const max = Math.max(0, t - v)
|
|
215
|
+
const o = Math.max(0, Math.round(offset))
|
|
216
|
+
return Math.min(o, max)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Move a scroll offset by one wheel `step`. Newest-first lists count the offset
|
|
221
|
+
* from the newest row, so "down" goes toward older rows and "up" back to the
|
|
222
|
+
* newest. The result stays inside the same clamped window as `clampScrollOffset`.
|
|
223
|
+
*/
|
|
224
|
+
export function scrollByStep(
|
|
225
|
+
total: number,
|
|
226
|
+
visible: number,
|
|
227
|
+
offset: number,
|
|
228
|
+
direction: "up" | "down",
|
|
229
|
+
step: number,
|
|
230
|
+
): number {
|
|
231
|
+
const s = Math.max(1, Math.round(step))
|
|
232
|
+
const next = direction === "down" ? offset + s : offset - s
|
|
233
|
+
return clampScrollOffset(total, visible, next)
|
|
234
|
+
}
|
|
@@ -1,89 +1,99 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* oes.json — OpenCode Extended Sidebar options.
|
|
3
|
-
* Merge: plugin defaults < ~/.config/opencode/oes.json < <project>/oes.json
|
|
4
|
-
*/
|
|
5
|
-
import path from "node:path"
|
|
6
|
-
import { fileStamp, getOpenCodeConfigDir, pluginRoot, readJson } from "./pware.oc.core.paths.js"
|
|
7
|
-
import { createStampCache } from "./pware.oc.core.cache.js"
|
|
8
|
-
|
|
9
|
-
export type OesOptions = {
|
|
10
|
-
fileRows: number
|
|
11
|
-
/** Sessions compared under Perf → History. 0 hides the section. */
|
|
12
|
-
perfHistory: number
|
|
13
|
-
/** Rows per Perf section (models, tools, history). */
|
|
14
|
-
perfRows: number
|
|
15
|
-
/** How many recent turns Perf scans. Higher = longer history, slower read. */
|
|
16
|
-
perfTurns: number
|
|
17
|
-
/** Seconds between full open-question rescans when no event-driven hint arrives. */
|
|
18
|
-
questionReconcileSec: number
|
|
19
|
-
/** Sessions fetched for the recent-sessions window. */
|
|
20
|
-
sessionFetch: number
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (!
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
1
|
+
/**
|
|
2
|
+
* oes.json — OpenCode Extended Sidebar options.
|
|
3
|
+
* Merge: plugin defaults < ~/.config/opencode/oes.json < <project>/oes.json
|
|
4
|
+
*/
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
import { fileStamp, getOpenCodeConfigDir, pluginRoot, readJson } from "./pware.oc.core.paths.js"
|
|
7
|
+
import { createStampCache } from "./pware.oc.core.cache.js"
|
|
8
|
+
|
|
9
|
+
export type OesOptions = {
|
|
10
|
+
fileRows: number
|
|
11
|
+
/** Sessions compared under Perf → History. 0 hides the section. */
|
|
12
|
+
perfHistory: number
|
|
13
|
+
/** Rows per Perf section (models, tools, history). */
|
|
14
|
+
perfRows: number
|
|
15
|
+
/** How many recent turns Perf scans. Higher = longer history, slower read. */
|
|
16
|
+
perfTurns: number
|
|
17
|
+
/** Seconds between full open-question rescans when no event-driven hint arrives. */
|
|
18
|
+
questionReconcileSec: number
|
|
19
|
+
/** Sessions fetched for the recent-sessions window. */
|
|
20
|
+
sessionFetch: number
|
|
21
|
+
/** How old (in hours) a session can be before it is dimmed in the recent-sessions window. Always ≤ sessionVisibleHours. */
|
|
22
|
+
sessionDimHours: number
|
|
23
|
+
/** How old (in hours) a session can be before it is hidden from the recent-sessions window. */
|
|
24
|
+
sessionVisibleHours: number
|
|
25
|
+
/** Hide Files that match the project's root .gitignore. Off by default. */
|
|
26
|
+
skipGitignore: boolean
|
|
27
|
+
toolRows: number
|
|
28
|
+
/** Tool-call history kept for the feed's `… +N more` revealer. Distinct from `toolRows`. */
|
|
29
|
+
toolFetch: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const OES_DEFAULTS: OesOptions = {
|
|
33
|
+
fileRows: 8,
|
|
34
|
+
perfHistory: 3,
|
|
35
|
+
perfRows: 5,
|
|
36
|
+
perfTurns: 120,
|
|
37
|
+
questionReconcileSec: 15,
|
|
38
|
+
sessionDimHours: 48,
|
|
39
|
+
sessionFetch: 10,
|
|
40
|
+
sessionVisibleHours: 72,
|
|
41
|
+
skipGitignore: false,
|
|
42
|
+
toolRows: 5,
|
|
43
|
+
toolFetch: 20,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function pluginOesPath(): string {
|
|
47
|
+
return path.join(pluginRoot(), "oes.json")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function clamp(n: unknown, min: number, max: number, fallback: number): number {
|
|
51
|
+
if (typeof n !== "number" || !Number.isFinite(n)) return fallback
|
|
52
|
+
return Math.round(Math.min(max, Math.max(min, n)))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Merge one oes.json object onto a base. Exported for tests. */
|
|
56
|
+
export function pick(raw: Record<string, unknown> | null, base: OesOptions): OesOptions {
|
|
57
|
+
if (!raw) return base
|
|
58
|
+
const toolRows = clamp(raw.toolRows, 3, 20, base.toolRows)
|
|
59
|
+
const sessionVisibleHours = clamp(raw.sessionVisibleHours, 1, 720, base.sessionVisibleHours)
|
|
60
|
+
const sessionDimHours = clamp(raw.sessionDimHours, 0, sessionVisibleHours, Math.min(base.sessionDimHours, sessionVisibleHours))
|
|
61
|
+
return {
|
|
62
|
+
fileRows: clamp(raw.fileRows, 3, 20, base.fileRows),
|
|
63
|
+
perfHistory: clamp(raw.perfHistory, 0, 10, base.perfHistory),
|
|
64
|
+
perfRows: clamp(raw.perfRows, 3, 20, base.perfRows),
|
|
65
|
+
perfTurns: clamp(raw.perfTurns, 20, 500, base.perfTurns),
|
|
66
|
+
questionReconcileSec: clamp(raw.questionReconcileSec, 5, 300, base.questionReconcileSec),
|
|
67
|
+
sessionDimHours,
|
|
68
|
+
sessionFetch: clamp(raw.sessionFetch, 2, 80, base.sessionFetch),
|
|
69
|
+
sessionVisibleHours,
|
|
70
|
+
skipGitignore: typeof raw.skipGitignore === "boolean" ? raw.skipGitignore : base.skipGitignore,
|
|
71
|
+
toolRows,
|
|
72
|
+
toolFetch: clamp(raw.toolFetch, toolRows, 80, base.toolFetch),
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function oesPaths(projectRoot?: string | null): string[] {
|
|
77
|
+
const out = [pluginOesPath(), path.join(getOpenCodeConfigDir(), "oes.json")]
|
|
78
|
+
if (projectRoot) out.push(path.join(projectRoot, "oes.json"))
|
|
79
|
+
return out
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function oesStamp(projectRoot?: string | null): string {
|
|
83
|
+
return oesPaths(projectRoot).map(fileStamp).join("|")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const oesCache = createStampCache<OesOptions>()
|
|
87
|
+
|
|
88
|
+
/** Drop the merged-options cache so the next getOes re-reads files. */
|
|
89
|
+
export function resetOesCache(): void {
|
|
90
|
+
oesCache.reset()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getOes(projectRoot?: string | null): OesOptions {
|
|
94
|
+
return oesCache.get(oesStamp(projectRoot), () => {
|
|
95
|
+
let next = { ...OES_DEFAULTS }
|
|
96
|
+
for (const p of oesPaths(projectRoot)) next = pick(readJson(p), next)
|
|
97
|
+
return next
|
|
98
|
+
})
|
|
99
|
+
}
|