Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/native-sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,62 @@ jobs:
run: |
npm install --prefix "$RUNNER_TEMP/native-host" --package-lock=false @opencode/cli@2.0.18
OPENCODE2_BINARY="$RUNNER_TEMP/native-host/node_modules/.bin/opencode2" node scripts/native-status-host-canary.mjs

strict-consumer:
name: Strict npm Goal TUI + OpenCode ${{ matrix.host }} / OpenTUI ${{ matrix.opentui }} (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
opentui: '0.5.14'
host: '2.0.18'
- os: windows-latest
opentui: '0.5.14'
host: '2.0.18'
- os: ubuntu-latest
opentui: '0.7.0'
host: '2.1.4'
- os: windows-latest
opentui: '0.7.0'
host: '2.1.4'
Comment thread
ByBrawe marked this conversation as resolved.
Resolved
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: '1.4.2'
- name: Build candidate without changing published version
run: |
npm install
npm run check
npm run build
- name: Clean strictly resolved package install + real TUI render
shell: bash
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/goal-packages"
npm pack --json --ignore-scripts --pack-destination "$RUNNER_TEMP/goal-packages" > "$RUNNER_TEMP/goal-pack.json"
filename="$(node -e 'const fs=require("node:fs");const p=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));if(p.length!==1)process.exit(1);process.stdout.write(p[0].filename)' "$RUNNER_TEMP/goal-pack.json")"
consumer="$RUNNER_TEMP/goal-clean-consumer"
mkdir -p "$consumer"
printf '{"private":true,"type":"module"}\n' > "$consumer/package.json"
# Strict npm resolution: NEVER --legacy-peer-deps, --force,
# --omit=peer or a developer-checkout node_modules link.
npm install --prefix "$consumer" --no-audit --no-fund --package-lock=false \
"$RUNNER_TEMP/goal-packages/$filename" \
"@opencode/cli@${{ matrix.host }}" \
"@opentui/core@${{ matrix.opentui }}" \
"@opentui/solid@${{ matrix.opentui }}" \
solid-js@1.9.12
npm ls --prefix "$consumer" @opencode/cli @opencode/plugin @bybrawe/opencode-goal @opentui/core @opentui/solid solid-js
node -e 'const p=require(process.argv[1]);if(p.version!==process.argv[2])process.exit(1);console.log("Host OpenCode CLI",p.version)' "$consumer/node_modules/@opencode/cli/package.json" "${{ matrix.host }}"
node -e 'const p=require(process.argv[1]);if(p.version!==process.argv[2])process.exit(1);console.log("OpenTUI Solid",p.version)' "$consumer/node_modules/@opentui/solid/package.json" "${{ matrix.opentui }}"
cp scripts/native-tui-clean-consumer-smoke.mjs "$consumer/native-tui-smoke.mjs"
(cd "$consumer" && bun --conditions=browser native-tui-smoke.mjs)
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@
"solid-js": "1.9.12"
},
"peerDependencies": {
"@opentui/core": ">=0.5.14 <0.6",
"@opentui/solid": ">=0.5.14 <0.6",
"@opentui/core": ">=0.5.14 <0.8",
"@opentui/solid": ">=0.5.14 <0.8",
"solid-js": ">=1.9.0 <2"
},
"peerDependenciesMeta": {
Expand Down
74 changes: 74 additions & 0 deletions scripts/native-tui-clean-consumer-smoke.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import assert from "node:assert/strict"
import { testRender } from "@opentui/solid"
import { createSignal } from "solid-js"
import plugin from "@bybrawe/opencode-goal/tui"

// This file runs only after a fresh strict npm installation of the packed
// candidate into a *different* directory. None of the checkout's dev
// dependencies should resolve from this consumer location.
let slot
let listener
let unregistered = 0
let unsubscribed = 0
const requests = []
const [selected, select] = createSignal("ses_first")
const project = (id) => "/remote/project/" + id

const ctx = {
client: { rpc: (definition) => {
assert.equal(definition.id, "opencode-goal-status")
return { read: async ({ sessionID }, options) => {
requests.push({ sessionID, location: options.location })
return {
schemaVersion: 1, sessionID,
directory: options.location.directory,
text: "OpenCode Goals\nACTIVE " + sessionID,
}
} }
} },
theme: { text: { base: "#ffffff" } },
data: {
session: { get: (id) => ({ id, location: { directory: project(id), workspaceID: "w1" } }) },
listen: (callback) => { listener = callback; return () => { unsubscribed++ } },
},
ui: { slot: (contribution) => {
assert.equal(contribution.append, "sidebar.content")
slot = contribution
return () => { unregistered++ }
} },
}

assert.equal(plugin.id, "opencode-goal", "native TUI plugin identity must match its established public registration")
const close = await plugin.setup(ctx)
assert.ok(slot)
const rendered = await testRender(
() => slot.render({ get sessionID() { return selected() } }),
{ width: 60, height: 12 },
)

try {
await new Promise((r) => setTimeout(r, 120))
await rendered.renderOnce()
assert.match(rendered.captureCharFrame(), /OpenCode Goals/)
assert.match(rendered.captureCharFrame(), /ACTIVE ses_first/)
select("ses_second")
await new Promise((r) => setTimeout(r, 120))
await rendered.renderOnce()
assert.match(rendered.captureCharFrame(), /ACTIVE ses_second/)
assert.doesNotMatch(rendered.captureCharFrame(), /ACTIVE ses_first/)
assert.equal(requests.at(-1).location.directory, project("ses_second"))
assert.equal(requests.at(-1).location.workspaceID, "w1")

close()
close()
const previous = requests.length
listener({ details: { type: "server.connected" } })
await new Promise((r) => setTimeout(r, 135))
assert.equal(requests.length, previous, "disposed sidebar must not keep polling")
assert.equal(unregistered, 1, "slot unregisters exactly once")
assert.equal(unsubscribed, 1, "listener unsubscribes exactly once")
console.log("Packed Goal strict npm consumer: OpenTUI visible sidebar, session switch and teardown PASS")
} finally {
close()
rendered.renderer.destroy()
}
Loading