Skip to content

fix(grunt/tui): proper hover-preview + click for #N ticket refs#14

Merged
terrxo merged 1 commit into
devfrom
grunt-fix/ticket-ref-hover-and-click
May 27, 2026
Merged

fix(grunt/tui): proper hover-preview + click for #N ticket refs#14
terrxo merged 1 commit into
devfrom
grunt-fix/ticket-ref-hover-and-click

Conversation

@terrxo

@terrxo terrxo commented May 27, 2026

Copy link
Copy Markdown

Closes hivemind anomalyco#233 properly (replaces the v1 markdown-link approach that rendered ugly [label](url) literals).

Root cause of the ugliness

Opentui's markdown renderer renders inline links as the LITERAL bracket+label+paren+URL string. The relevant block in node_modules/.bun/@opentui+core/index*.js:

case 'link':
  chunks.push(this.createChunk('[', 'markup.link', ...))
  // ...render label tokens...
  chunks.push(this.createChunk('](', 'markup.link', ...))
  chunks.push(this.createChunk(token.href, 'markup.link.url', ...))
  chunks.push(this.createChunk(')', 'markup.link', ...))

Even in conceal mode the URL leaks as <label> (url). There's no inline-token renderNode override hook either. So markdown links can't give us the clean visual + hover + click experience Nik described.

New path: bypass markdown for ticket refs

Split assistant text on the ticket-ref regex BEFORE markdown sees it. Render non-matching segments through markdown as before. Render each matched #N as a new <TicketRef> component.

TicketRef (packages/opencode/src/cli/cmd/tui/component/ticket-ref.tsx):

  • Renders #229 colored (theme.markdownLink) + underlined inline
  • onMouseOver: lazy-fetches /api/tasks/<id> from hivemind-api (module-level cache + in-flight dedup), shows a floating tooltip with title + status + priority + zone + owner + scope preview
  • onMouseUp: opens http://localhost:5173/tasks/<id> via the open npm package (same as existing <Link> component)

Trade-off: inline markdown features spanning a #N boundary (e.g. **bold #229 text**) won't span the split. Acceptable for typical prose.

Bundled: install-local.sh version stamping fix

Working tree contained an uncommitted update from a parallel agent during this session. Changes the default version label from git describe --dirty (which made local builds look version-ahead of any released gruntcode → broke brew-binary boot when local migrations had advanced the DB) to a semver build-metadata format like 1.15.10-grunt.7+local.<sha>. Includes ahead-of-tag detection that warns about new migrations.

Included here because it was needed for THIS install to not break on next brew upgrade.

Verified

bun typecheck clean. Local install reports 1.15.10-grunt.7+local.5573d8375.dirty.

…aces ugly [label](url))

Refs hivemind anomalyco#233. Closes the 'ugly link being showed' regression Nik
flagged immediately after the first hover-preview attempt.

## What changed

Previous shipped path: pre-process text to inject markdown links
`[anomalyco#229](http://localhost:5173/tasks/229)`. Opentui's markdown renderer
displays inline links as the LITERAL bracket+label+paren+URL string
(see node_modules/.bun/@OpenTui+core/index*.js renderInlineToken 'link'
case). Even in conceal mode the URL leaks as `<label> (url)`. Result:
colored but visually ugly, no hover, no click handler.

New path: split assistant text on the ticket-ref regex BEFORE markdown
sees it. Render non-matching segments through markdown as before (so
formatting + code blocks keep working). Render each matched `#N` as a
new <TicketRef> component (packages/opencode/src/cli/cmd/tui/component/ticket-ref.tsx).

## TicketRef component

- Renders `anomalyco#229` colored (theme.markdownLink) + underlined inline
- onMouseOver: lazy-fetches /api/tasks/<id> from hivemind-api (with
  module-level cache + in-flight dedup), shows a floating tooltip
  with title + status + priority + zone + owner + scope preview
- onMouseUp: opens http://localhost:5173/tasks/<id> via 'open' npm pkg
  (same package the existing Link component uses)

Trade-off: inline markdown features that span a #N boundary (rare —
e.g. `**bold anomalyco#229 text**`) won't span the split. Acceptable for prose.

## Bundled fix: install-local.sh version stamping

Working tree also contained an uncommitted update to scripts/install-local.sh
(originated from a parallel agent during this session). It changes the
default version label from `git describe --dirty` (which made local
builds look version-ahead of any released gruntcode → broke
brew-binary boot when local migrations had advanced the DB) to a
semver build-metadata format like `1.15.10-grunt.7+local.<sha>`. The
base matches the latest grunt tag; the +local.<sha> part is semver
build-metadata that's ignored for ordering. Also adds ahead-of-tag
detection that warns about new migrations.

Including it here since it was needed for THIS install to not break
on next brew upgrade.
@terrxo terrxo merged commit 8b07799 into dev May 27, 2026
3 of 10 checks passed
@terrxo terrxo deleted the grunt-fix/ticket-ref-hover-and-click branch May 27, 2026 00:30
@github-actions

Copy link
Copy Markdown

Hey! Your PR title fix(grunt/tui): proper hover-preview + click for #N ticket refs doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

terrxo added a commit that referenced this pull request May 27, 2026
…lyco#258) (#19)

Add a smoke-test step that runs BEFORE the freshly-built binary replaces
the brew Cellar binary. Catches the class of regression where the build
succeeds at the bundler layer but the produced binary crashes at
module-load / plugin-resolution / config-read — the surface that
silently shipped a U.length crash 2026-05-27 (PR #14-17 era) and went
undetected for ~1h because already-running tabs kept their mmap'd old
binary.

Two layers, both cheap (~0.7s total on a healthy binary):
  1. `--version`      — catches module-load + top-level import crashes
  2. `debug info`     — catches plugin-resolution + config-load
                        regressions (exercises the same module graph the
                        TUI mount path does)

If either layer fails — non-zero exit, OR exit 0 with TypeError /
ReferenceError / SyntaxError / fatal-error / U.length / 'Cannot read
prop' on stderr — the install aborts and the working brew binary stays
put. The build output is preserved at $SRC_BIN for inspection.

`--skip-smoke` flag provided for emergency overrides (rebuild loops on
a known-broken state where the new binary is wanted despite a smoke
fail).

`timeout` wrapper detection: macOS doesn't ship coreutils by default,
so `timeout`/`gtimeout` are detected at runtime; absent both, the smoke
runs unwrapped (the commands themselves exit fast, so the wrapper is
belt-and-suspenders, not load-bearing).

Verified locally with three integration cases against the in-script
smoke block:
  - Healthy gruntcode binary → proceeds to install
  - Crashing fake-bin → refused + exit 1, install aborted
  - Stealth crash (exit 0 + TypeError on stderr) → refused + exit 1
  - `--skip-smoke` → smoke skipped, install proceeds

Help text + doc-comment header updated; new `Smoke-test (pre-install
verification)` section explains what the smoke does + how to disable.

Closes anomalyco#258.
terrxo added a commit that referenced this pull request May 28, 2026
…aces ugly [label](url)) (#14)

Refs hivemind anomalyco#233. Closes the 'ugly link being showed' regression Nik
flagged immediately after the first hover-preview attempt.

## What changed

Previous shipped path: pre-process text to inject markdown links
`[anomalyco#229](http://localhost:5173/tasks/229)`. Opentui's markdown renderer
displays inline links as the LITERAL bracket+label+paren+URL string
(see node_modules/.bun/@OpenTui+core/index*.js renderInlineToken 'link'
case). Even in conceal mode the URL leaks as `<label> (url)`. Result:
colored but visually ugly, no hover, no click handler.

New path: split assistant text on the ticket-ref regex BEFORE markdown
sees it. Render non-matching segments through markdown as before (so
formatting + code blocks keep working). Render each matched `#N` as a
new <TicketRef> component (packages/opencode/src/cli/cmd/tui/component/ticket-ref.tsx).

## TicketRef component

- Renders `anomalyco#229` colored (theme.markdownLink) + underlined inline
- onMouseOver: lazy-fetches /api/tasks/<id> from hivemind-api (with
  module-level cache + in-flight dedup), shows a floating tooltip
  with title + status + priority + zone + owner + scope preview
- onMouseUp: opens http://localhost:5173/tasks/<id> via 'open' npm pkg
  (same package the existing Link component uses)

Trade-off: inline markdown features that span a #N boundary (rare —
e.g. `**bold anomalyco#229 text**`) won't span the split. Acceptable for prose.

## Bundled fix: install-local.sh version stamping

Working tree also contained an uncommitted update to scripts/install-local.sh
(originated from a parallel agent during this session). It changes the
default version label from `git describe --dirty` (which made local
builds look version-ahead of any released gruntcode → broke
brew-binary boot when local migrations had advanced the DB) to a
semver build-metadata format like `1.15.10-grunt.7+local.<sha>`. The
base matches the latest grunt tag; the +local.<sha> part is semver
build-metadata that's ignored for ordering. Also adds ahead-of-tag
detection that warns about new migrations.

Including it here since it was needed for THIS install to not break
on next brew upgrade.
terrxo added a commit that referenced this pull request May 28, 2026
…lyco#258) (#19)

Add a smoke-test step that runs BEFORE the freshly-built binary replaces
the brew Cellar binary. Catches the class of regression where the build
succeeds at the bundler layer but the produced binary crashes at
module-load / plugin-resolution / config-read — the surface that
silently shipped a U.length crash 2026-05-27 (PR #14-17 era) and went
undetected for ~1h because already-running tabs kept their mmap'd old
binary.

Two layers, both cheap (~0.7s total on a healthy binary):
  1. `--version`      — catches module-load + top-level import crashes
  2. `debug info`     — catches plugin-resolution + config-load
                        regressions (exercises the same module graph the
                        TUI mount path does)

If either layer fails — non-zero exit, OR exit 0 with TypeError /
ReferenceError / SyntaxError / fatal-error / U.length / 'Cannot read
prop' on stderr — the install aborts and the working brew binary stays
put. The build output is preserved at $SRC_BIN for inspection.

`--skip-smoke` flag provided for emergency overrides (rebuild loops on
a known-broken state where the new binary is wanted despite a smoke
fail).

`timeout` wrapper detection: macOS doesn't ship coreutils by default,
so `timeout`/`gtimeout` are detected at runtime; absent both, the smoke
runs unwrapped (the commands themselves exit fast, so the wrapper is
belt-and-suspenders, not load-bearing).

Verified locally with three integration cases against the in-script
smoke block:
  - Healthy gruntcode binary → proceeds to install
  - Crashing fake-bin → refused + exit 1, install aborted
  - Stealth crash (exit 0 + TypeError on stderr) → refused + exit 1
  - `--skip-smoke` → smoke skipped, install proceeds

Help text + doc-comment header updated; new `Smoke-test (pre-install
verification)` section explains what the smoke does + how to disable.

Closes anomalyco#258.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant