@madsoftwaredev/opencode-chat-title 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -15
- package/header.ts +16 -15
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -35,15 +35,15 @@ Do not also load the original local `plugins/chat-title` copy: it has the same
|
|
|
35
35
|
|
|
36
36
|
## Compatibility and limitations
|
|
37
37
|
|
|
38
|
-
The supported development baseline is **OpenCode 2.0
|
|
38
|
+
The supported development baseline is **OpenCode 2.1.0**, **OpenTUI 0.6.6**,
|
|
39
39
|
and **Solid 1.9.12**. This is not a guarantee that every later OpenCode layout
|
|
40
40
|
will work. The package exports `./tui` only and runs in the OpenCode terminal,
|
|
41
41
|
not in the web or desktop UI. Its Node `>=22` engine metadata does not make the
|
|
42
42
|
TypeScript/TSX entrypoint a standalone Node program.
|
|
43
43
|
|
|
44
44
|
OpenCode V2 publishes no slot above the transcript. This plugin uses the exposed
|
|
45
|
-
OpenTUI
|
|
46
|
-
into the session column that owns the `session.composer.top` slot.
|
|
45
|
+
OpenTUI render tree and renderable style getters, not a browser DOM, to insert
|
|
46
|
+
the header into the session column that owns the `session.composer.top` slot.
|
|
47
47
|
|
|
48
48
|
Sidebar visibility is tracked through the documented `sidebar.content` slot,
|
|
49
49
|
which mounts while the sidebar renders. Replacing that slot can interfere with
|
|
@@ -52,16 +52,17 @@ this visibility signal.
|
|
|
52
52
|
The adapter matches the layout inspected in OpenCode 2.0.27: exactly three rows
|
|
53
53
|
for the transcript (a box wrapping the scrollbox), the one-row "jump to latest"
|
|
54
54
|
bar, and the composer branch containing the plugin anchor. Unrecognized layouts
|
|
55
|
-
are left alone and reported with a warning toast.
|
|
56
|
-
|
|
55
|
+
are left alone and reported with a warning toast. Errors during initial layout
|
|
56
|
+
discovery also leave the transcript intact rather than crashing the composer
|
|
57
|
+
slot. OpenCode updates or other plugins that change that tree may require an
|
|
58
|
+
adapter update. No installed
|
|
57
59
|
binaries or OpenCode source files are patched.
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
layout guarantees.
|
|
61
|
+
Version 0.1.1 replaces the removed `getLayoutNode()` API with renderable style
|
|
62
|
+
getters. The header requests a frame after insertion and uses `onSizeChange` to
|
|
63
|
+
refit its text after native layout and terminal resizes. It never accesses or
|
|
64
|
+
modifies a raw Yoga node. Host structure remains an implementation dependency,
|
|
65
|
+
not a public layout guarantee.
|
|
65
66
|
|
|
66
67
|
OpenCode supplies the UI runtime and rewrites host imports in `tui.tsx`.
|
|
67
68
|
`header.ts` and `title.ts` deliberately avoid runtime imports of UI packages so
|
|
@@ -72,14 +73,15 @@ the adapter receives the host's own constructors.
|
|
|
72
73
|
Install workspace dependencies once from the repository root, then run:
|
|
73
74
|
|
|
74
75
|
```sh
|
|
75
|
-
bun
|
|
76
|
+
bun run --cwd packages/chat-title test
|
|
76
77
|
bun run --cwd packages/chat-title typecheck
|
|
77
78
|
bun build ./packages/chat-title/tui.tsx --target=bun --external '@opencode/*' --external '@opentui/*' --external 'solid-js'
|
|
78
79
|
```
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
Unit tests cover title fitting and render-tree matching. Headless OpenTUI 0.6.6
|
|
82
|
+
integration tests cover rendering, title updates, narrow-terminal resizing,
|
|
83
|
+
sidebar visibility, saved-toggle wiring, disposal, and safe layout rejection.
|
|
84
|
+
They use the inspected host shape, not a live installed OpenCode release.
|
|
83
85
|
The build command checks transpilation, not host integration. Development
|
|
84
86
|
dependencies are not needed to load the plugin in OpenCode.
|
|
85
87
|
|
package/header.ts
CHANGED
|
@@ -18,16 +18,14 @@ export type YogaLayout = {
|
|
|
18
18
|
getHeight(): YogaValue
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export type Node = {
|
|
21
|
+
export type Node = YogaLayout & {
|
|
22
22
|
readonly isDestroyed: boolean
|
|
23
23
|
readonly parent: Node | null
|
|
24
24
|
readonly width: number
|
|
25
25
|
getChildren(): Node[]
|
|
26
|
-
|
|
26
|
+
onSizeChange?: () => void
|
|
27
27
|
add(child: unknown, index?: number): number
|
|
28
28
|
destroy(): void
|
|
29
|
-
on(event: string, handler: () => void): void
|
|
30
|
-
off(event: string, handler: () => void): void
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
export type LabelNode = Node & { content: string }
|
|
@@ -67,20 +65,19 @@ function isColumn(node: Node, branch: Node, core: Core): boolean {
|
|
|
67
65
|
if (!(children[0] instanceof core.BoxRenderable)) return false
|
|
68
66
|
if (!(children[0].getChildren()[0] instanceof core.ScrollBoxRenderable)) return false
|
|
69
67
|
|
|
70
|
-
const jump = children[1]
|
|
68
|
+
const jump = children[1]
|
|
71
69
|
const jumpHeight = jump.getHeight()
|
|
72
70
|
if (jumpHeight.unit !== core.Yoga.Unit.Point || jumpHeight.value !== 1) return false
|
|
73
71
|
if (jump.getFlexShrink() !== 0) return false
|
|
74
72
|
|
|
75
|
-
const
|
|
76
|
-
const padding = layout.getPadding(core.Yoga.Edge.Bottom)
|
|
73
|
+
const padding = node.getPadding(core.Yoga.Edge.Bottom)
|
|
77
74
|
if (padding.unit !== core.Yoga.Unit.Point || padding.value !== 1) return false
|
|
78
|
-
if (
|
|
75
|
+
if (node.getFlexGrow() !== 1) return false
|
|
79
76
|
|
|
80
77
|
// The column sits beside the sidebar, so its own parent is a row.
|
|
81
78
|
const parent = node.parent
|
|
82
79
|
if (!parent) return false
|
|
83
|
-
return parent.
|
|
80
|
+
return parent.getFlexDirection() === core.Yoga.FlexDirection.Row
|
|
84
81
|
}
|
|
85
82
|
|
|
86
83
|
/**
|
|
@@ -101,7 +98,13 @@ export function findSessionColumn(anchor: Node, core: Core): Node | undefined {
|
|
|
101
98
|
* flexbox, so the header takes a row from the transcript instead of covering it.
|
|
102
99
|
*/
|
|
103
100
|
export function attachHeader(options: AttachOptions): Attachment | undefined {
|
|
104
|
-
|
|
101
|
+
let column: Node | undefined
|
|
102
|
+
try {
|
|
103
|
+
column = findSessionColumn(options.anchor, options.core)
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.warn("[chat-title] Unsupported render tree; the chat title is hidden", error)
|
|
106
|
+
return undefined
|
|
107
|
+
}
|
|
105
108
|
if (!column) return undefined
|
|
106
109
|
|
|
107
110
|
const header = new options.core.BoxRenderable(options.renderer, {
|
|
@@ -121,8 +124,7 @@ export function attachHeader(options: AttachOptions): Attachment | undefined {
|
|
|
121
124
|
})
|
|
122
125
|
header.add(label)
|
|
123
126
|
column.add(header, 0)
|
|
124
|
-
//
|
|
125
|
-
// measured on the next pass and "resize" fires with a real width.
|
|
127
|
+
// Request a frame so native layout measures the row and invokes onSizeChange.
|
|
126
128
|
options.renderer.requestRender?.()
|
|
127
129
|
|
|
128
130
|
let disposed = false
|
|
@@ -138,8 +140,7 @@ export function attachHeader(options: AttachOptions): Attachment | undefined {
|
|
|
138
140
|
label.content = next
|
|
139
141
|
}
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
header.on("resize", refresh)
|
|
143
|
+
header.onSizeChange = refresh
|
|
143
144
|
refresh()
|
|
144
145
|
|
|
145
146
|
return {
|
|
@@ -147,7 +148,7 @@ export function attachHeader(options: AttachOptions): Attachment | undefined {
|
|
|
147
148
|
dispose() {
|
|
148
149
|
if (disposed) return
|
|
149
150
|
disposed = true
|
|
150
|
-
header.
|
|
151
|
+
if (!header.isDestroyed) header.onSizeChange = undefined
|
|
151
152
|
// The host detaches children without destroying them, so drop the label
|
|
152
153
|
// first and then the row itself.
|
|
153
154
|
if (!label.isDestroyed) label.destroy()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madsoftwaredev/opencode-chat-title",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "OpenCode V2 TUI plugin that shows the session title above the transcript while the sidebar is hidden.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -35,19 +35,21 @@
|
|
|
35
35
|
"node": ">=22"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
|
-
"test": "bun test",
|
|
38
|
+
"test": "bun test --preload @opentui/solid/preload",
|
|
39
39
|
"typecheck": "tsc --noEmit"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@opencode/plugin": "^2.0
|
|
42
|
+
"@opencode/plugin": "^2.1.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@opentui/core": ">=0.
|
|
46
|
-
"@opentui/solid": ">=0.
|
|
45
|
+
"@opentui/core": ">=0.6.6",
|
|
46
|
+
"@opentui/solid": ">=0.6.6",
|
|
47
47
|
"solid-js": ">=1.9.12"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@
|
|
50
|
+
"@opentui/core": "0.8.1",
|
|
51
|
+
"@opentui/solid": "0.8.1",
|
|
52
|
+
"@types/bun": "1.4.0",
|
|
51
53
|
"typescript": "5.8.2"
|
|
52
54
|
},
|
|
53
55
|
"publishConfig": {
|