Skip to content

feat: add forProject() + useProjectBus + deriveProjectId — project-scoped bus v0.6.7#16

Merged
four-bytes-robby merged 11 commits into
mainfrom
fix/13-memory-bus-upgrade
Jun 15, 2026
Merged

feat: add forProject() + useProjectBus + deriveProjectId — project-scoped bus v0.6.7#16
four-bytes-robby merged 11 commits into
mainfrom
fix/13-memory-bus-upgrade

Conversation

@four-bytes-robby

@four-bytes-robby four-bytes-robby commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds forProject(id) to BusClient and BusTui (and their scoped variants) — completes the planned scope triad alongside forService and forSession
  • Includes earlier commits: useProjectBus hook, deriveProjectId (FNV-1a hash), BusTui/MemoryBusTui exports, ping-retry fix, cancellation-safe connect
  • Bumps to v0.6.7

Closes #15

Test plan

  • forProject chains correctly: bus.forService("brain").forProject(id).publish("status", ...) → channel brain/{id}/status
  • useProjectBus("brain", directory, "status", cb) subscribes and receives messages on project channel
  • four-opencode-brain startup/ingest status flows to TUI via project channel (tested via brain v1.7.8)

🤖 Generated with Claude Code


Summary by cubic

Adds project-scoped bus channels via forProject(), useProjectBus, and deriveProjectId so plugins can route messages per project. Also removes the TUI’s silent fallback: BusTui.connect() now throws if the Go bus is unavailable; opt in to MemoryBusTui for in-process mode (v0.7.1).

  • New Features

    • BusClient/BusTui: forProject(id) prefixes channels with {projectId}/ and composes with forService/forSession.
    • useProjectBus(service, directory, channel, cb) + deriveProjectId(directory) (FNV-1a); adds useServiceBus with cancellation-safe connect, cleanup, and retry.
    • Exports: MemoryBusTui, useServiceBus, deriveProjectId; ignore dist/ in .gitignore.
  • Migration

    • BusTui.connect() now throws when the Go bus isn’t reachable; use new MemoryBusTui() for in-process mode or wrap in try/catch to handle degraded behavior.

Written for commit 1ee98f2. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added project-scoped channel support with automatic prefix isolation
    • Added convenience hook for project-based bus subscriptions
  • Chores

    • Version bump to 0.7.1
    • Updated build artifact exclusions

@four-bytes-robby four-bytes-robby self-assigned this Jun 15, 2026
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

Recent review info
Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: da9244d8-2ead-4973-92d1-456fc360b33c

Commits

Reviewing files that changed from the base of the PR and between b95c06f and 1ee98f2.

Files ignored due to path filters (6)
  • dist/bus-client.d.ts is excluded by !**/dist/**
  • dist/bus-tui.d.ts is excluded by !**/dist/**
  • dist/index.js is excluded by !**/dist/**
  • dist/index.js.map is excluded by !**/dist/**, !**/*.map
  • dist/tui.js is excluded by !**/dist/**
  • dist/tui.js.map is excluded by !**/dist/**, !**/*.map
Files selected for processing (5)
  • .gitignore
  • package.json
  • src/bus-client.ts
  • src/bus-tui.ts
  • src/tui.ts
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/bus-client.ts
  • src/tui.ts

Walkthrough

Walkthrough

BusClient and BusTui each gain a forProject(id) method that returns a scoped wrapper prefixing channel names with {projectId}/. The ScopedBusClient and ScopedBusTui classes receive matching overrides for prefix stacking. tui.ts adds useProjectBus, re-exports MemoryBusTui and deriveProjectId, and the package is bumped to 0.7.1.

Changes

Project-scoped bus channels and useProjectBus hook

Layer / File(s) Summary
forProject() scoping on BusClient and BusTui
src/bus-client.ts, src/bus-tui.ts
BusClient.forProject(id) and BusTui.forProject(id) create scoped wrappers that prefix all channel operations with {projectId}/. ScopedBusClient and ScopedBusTui each add an override forProject(id) that stacks the prefix on top of the existing one.
useProjectBus hook and public re-exports
src/tui.ts
Adds useProjectBus(service, directory, channel, onMessage) that computes projectId via deriveProjectId(directory) and delegates to useServiceBus. Re-exports MemoryBusTui and deriveProjectId from tui.ts.
Version bump and .gitignore update
package.json, .gitignore
Package version incremented from 0.7.0 to 0.7.1. dist/ added to .gitignore.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Possibly related PRs

  • four-bytes/four-opencode-plugin-lib#9: Implements the analogous forService()/forSession() scoped prefix pattern on the same BusClient and BusTui classes, establishing the design this PR follows for forProject().
  • four-bytes/four-opencode-plugin-lib#14: Introduces useServiceBus and deriveProjectId that the new useProjectBus hook in this PR re-exports and delegates to directly.
Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main features added: forProject() method, useProjectBus hook, and deriveProjectId function, with version bump.
Linked Issues check ✅ Passed All requirements from issue #15 are met: forProject() methods added to BusClient, ScopedBusClient, BusTui, and ScopedBusTui with prefix-based scoping mechanics.
Out of Scope Changes check ✅ Passed All changes are directly aligned with PR objectives: forProject() implementations, useProjectBus hook, deriveProjectId export, MemoryBusTui re-export, and .gitignore update.

Tip: You can configure your own custom pre-merge checks in the settings.

Finishing Touches
Generate docstrings
  • Create stacked PR
  • Commit on current branch
Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/13-memory-bus-upgrade
Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/13-memory-bus-upgrade

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Nitpick comments (1)
src/tui.ts (1)

24-31: 💤 Low value

Consider using forProject semantically in useProjectBus.

The implementation delegates to useServiceBus, which internally calls forSession(sid) to scope the channel. While this produces the correct channel prefix (since forSession and forProject are functionally identical), it is semantically misleading: a project-scoped hook is using session-scoped internals.

If useServiceBus were refactored to accept a generic scopeId parameter and a scoping strategy (or if a parallel useProjectBusInternal existed that uses forProject), the code would be clearer. For now, this works correctly but may cause confusion during maintenance.

Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tui.ts` around lines 24 - 31, The `useProjectBus` function delegates to
`useServiceBus`, which internally uses `forSession` for scoping, creating a
semantic mismatch because a project-scoped hook is using session-scoped
internals. To improve clarity during maintenance, refactor to make the scoping
semantics explicit: either modify `useServiceBus` to accept a scoping strategy
parameter that allows `useProjectBus` to pass `forProject` directly, or create a
separate `useProjectBusInternal` function that uses `forProject` instead of
relying on `useServiceBus` which uses `forSession`. This will ensure the code's
semantics clearly reflect its intent, even though the current implementation
produces functionally correct results.
Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ISSUES.md`:
- Around line 88-109: The ISSUES.md document contains a contradiction where line
117 states that Issue `#3` was fixed so that BusTui.connect() throws on missing
bus with useServiceBus as the recommended path, but the code example in lines
95-109 shows a different pattern where BusTui.connect() returns a reconnecting
instance instead of throwing. Either replace the code example to match the
actual throw-on-fail behavior that was implemented in commit ccde741, add a
clarifying note that the example shows an alternative approach rather than what
was actually fixed, or remove the code example entirely and simply reference
useServiceBus as the recommended solution to resolve this inconsistency.
- Around line 124-130: The ISSUES.md table shows lifecycle patterns for Issues
`#1` (TUI reconnect with port re-discovery via BusTui.scheduleReconnect()) and `#3`
(TUI avoiding MemoryBusTui dead-end via BusTui.connect()) marked as "❌ missing"
in lines 128-129, but these same issues are documented as "✅ FIXED" elsewhere in
the document (lines 44 and 117). Additionally, the document states that the
useServiceBus hook was extracted to centralize these lifecycle patterns. Update
the table rows for the TUI reconnect and MemoryBusTui patterns to reflect their
actual completion status (mark as done or clarify that they are now implemented
within the useServiceBus hook) rather than leaving them marked as missing.

---

Nitpick comments:
In `@src/tui.ts`:
- Around line 24-31: The `useProjectBus` function delegates to `useServiceBus`,
which internally uses `forSession` for scoping, creating a semantic mismatch
because a project-scoped hook is using session-scoped internals. To improve
clarity during maintenance, refactor to make the scoping semantics explicit:
either modify `useServiceBus` to accept a scoping strategy parameter that allows
`useProjectBus` to pass `forProject` directly, or create a separate
`useProjectBusInternal` function that uses `forProject` instead of relying on
`useServiceBus` which uses `forSession`. This will ensure the code's semantics
clearly reflect its intent, even though the current implementation produces
functionally correct results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

Review info
Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b86cce3f-ffb7-4db3-b5a8-54dcc4198cf1

Commits

Reviewing files that changed from the base of the PR and between 88cd19a and fa5afa7.

Files ignored due to path filters (12)
  • dist/bus-client.d.ts is excluded by !**/dist/**
  • dist/bus-tui.d.ts is excluded by !**/dist/**
  • dist/derive-project-id.d.ts is excluded by !**/dist/**
  • dist/index.d.ts is excluded by !**/dist/**
  • dist/index.js is excluded by !**/dist/**
  • dist/index.js.map is excluded by !**/dist/**, !**/*.map
  • dist/project-id.d.ts is excluded by !**/dist/**
  • dist/tui-components/progress-bar.jsx is excluded by !**/dist/**
  • dist/tui-hooks.d.ts is excluded by !**/dist/**
  • dist/tui.d.ts is excluded by !**/dist/**
  • dist/tui.js is excluded by !**/dist/**
  • dist/tui.js.map is excluded by !**/dist/**, !**/*.map
Files selected for processing (10)
  • ISSUES.md
  • package.json
  • scripts/build.ts
  • src/bus-client.ts
  • src/bus-tui.ts
  • src/derive-project-id.ts
  • src/index.ts
  • src/tui-hooks.ts
  • src/tui.ts
  • test/integration.test.ts

Comment thread ISSUES.md
Comment thread ISSUES.md

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found and verified against the latest diff

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/bus-tui.ts
Comment thread ISSUES.md
four-bytes-robby and others added 11 commits June 15, 2026 13:20
Closes #13

Removes the automatic fallback to MemoryBusTui when the Go bus is
not reachable. Callers that want in-process mode can now instantiate
'new MemoryBusTui()' explicitly. This prevents silent subscription
failures where a TUI subscribes to a pattern via MemoryBusTui, never
sees any messages (because the server-side plugin publishes via the
real Go bus), and the user has no idea why.

After this change:
- BusTui.connect() throws a clear error if the Go bus is unreachable
- MemoryBusTui is still exported for callers that want it explicitly
- Both consumers (brain, tbguard) already have try/catch or .catch
  handlers, so they degrade gracefully and log a warning
- Tests are not affected (integration test runs the real Go bus;
  scoped-close test also runs the real Go bus)

Updates MemoryBusTui JSDoc to note opt-in usage and the integration
test header comment to reflect the new contract.
Add unmount guard and reconnect-with-backoff to useServiceBus hook.
Prevents leaks when a TUI component unmounts while BusTui.connect()
is still in-flight (the resulting handle is closed immediately).
On connect failure, retries every 5s instead of silently giving up.

Ref: #13
Closes #15

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@four-bytes-robby four-bytes-robby force-pushed the fix/13-memory-bus-upgrade branch from b95c06f to 1ee98f2 Compare June 15, 2026 11:28
@four-bytes-robby four-bytes-robby merged commit d48f2fb into main Jun 15, 2026
2 of 3 checks passed
@four-bytes-robby four-bytes-robby deleted the fix/13-memory-bus-upgrade branch June 15, 2026 11:31
@four-bytes-robby

Copy link
Copy Markdown
Member Author

Re: cubic-dev-ai P2 — src/bus-tui.ts:46 reconnect loop leak

Confirmed valid. Here's the failure chain:

  1. connect()bus.open() registers ws.onclose → scheduleReconnect() (line 150)
  2. If open() throws mid-setup, the WebSocket may still fire onclose
  3. scheduleReconnect() loops back to open() → infinite retry on a dead instance
  4. connect() throws but the leaked BusTui instance keeps retrying forever

Fix: Call bus.close() in the catch block before re-throwing. This sets this.closed = true, so both onclose (line 152) and scheduleReconnect() (line 165) bail out immediately.

Will PR this as a follow-up to #16.

@four-bytes-robby

Copy link
Copy Markdown
Member Author

Re: coderabbitai & cubic-dev-ai — ISSUES.md table inconsistency (lines 128-129)

Valid. Lines 128-129 mark Issues #1 and #3 as "❌ missing" in the lifecycle patterns table, but:

The table hasn't been updated since the fixes landed. Will update both rows to reflect completion status. Also applies to the code example mismatch at lines 95-109 which shows the old scheduleReconnect() return pattern instead of the current throw-on-fail behavior.

@four-bytes-robby

Copy link
Copy Markdown
Member Author

Re: coderabbitai nitpick — src/tui.ts:24-31 useProjectBus semantic mismatch

Noted. The current implementation delegates useProjectBususeServiceBus, which internally uses forSession(sid) for channel scoping. While forSession and forProject produce functionally identical channel prefixes (both driven by the calling plugin's session context), the delegation path is semantically confusing. A reader sees "project" but the code says "session."

I'll keep this as-is for now because:

  • The channel prefix is correct regardless of naming
  • Refactoring useServiceBus to accept an explicit scoping function would be a broader change
  • This is a clarity concern, not a functional bug

Filed as a low-priority cleanup item for the next wave.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] Add forProject() scoped bus channels to BusClient and BusTui

1 participant