Skip to content

Feat/tabs - #6

Merged
Kitenite merged 4 commits into
mainfrom
feat/tabs
Oct 28, 2025
Merged

Kitenite merged 4 commits into
mainfrom
feat/tabs

Conversation

@Kitenite

Copy link
Copy Markdown
Member

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes

@vercel

vercel Bot commented Oct 28, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
superset-website Building Building Preview Comment Oct 28, 2025 6:31am

@Kitenite
Kitenite merged commit 82ea149 into main Oct 28, 2025
3 of 7 checks passed
@Kitenite
Kitenite deleted the feat/tabs branch October 28, 2025 06:31
AviPeltz added a commit that referenced this pull request May 10, 2026
Resolves 11 findings from greptile + coderabbit review on the
remote-control feature:

- #1 (P1): `remoteControl.get` is now `publicProcedure` and accepts the
  raw token, hashing it for constant-time comparison against the row's
  `tokenHash`. Anonymous viewers can resolve `wsUrl` without a Superset
  session — the share link itself is the credential.
- #10 (Major): the host-side `sendInput` no longer round-trips bytes
  through a latin1 string before `pty.write` re-encodes them as UTF-8
  (which corrupted any byte ≥ 0x80). Adds `pty.writeBytes` that
  forwards a `Uint8Array` straight to the daemon.
- #2: a single `cleanup()` helper now handles `onClose` and `onError`,
  removing the viewer from the session's set, detaching the handle, and
  unsubscribing the revoke listener idempotently. Fixes a leak where
  abrupt teardown could orphan up to four `MAX_VIEWERS` slots until host
  restart.
- #8: client WebSocket payloads are validated via a zod discriminated
  union before dispatch; `resize` and `runCommand` are wrapped in
  try/catch like `input` was.
- #5: `TerminalRemoteControlButton` hydrates from
  `remoteControl.listForWorkspace` on mount and refreshes every 30s, so
  the live badge survives remounts and reflects backend revocation /
  expiry. The original `webUrl` is unrecoverable after `create` (the
  cloud only stores `tokenHash`), so Copy Link is disabled when we
  don't hold it.
- #3: handshake-time auth result is cached on the WS context; per-
  message handling just compares `expiresAt` against `now` instead of
  re-running HMAC + SHA-256 at 200/s/viewer.
- #4: the bearer token is now passed in the URL fragment
  (`#remoteControlToken=…`), not the query string. The fragment never
  reaches the server, never appears in `Referer` headers, and stays out
  of access logs and history. A new `RemoteTerminalLoader` client
  component reads `location.hash` after mount.
- #7: the web viewer writes a one-time dim hint into xterm when the
  user types in `command` mode so silent drops are explained.
- #9: oversized PTY chunks (> 256 KB in one event) now have their tail
  preserved instead of being pushed-and-immediately-shifted out of the
  ring, which would have left late-joining viewers with an empty
  snapshot.
- #11: host-side mintToken schema now `.min(MIN_TTL).max(MAX_TTL)`,
  matching `mintRemoteControlToken`'s internal clamp.
- #12: revoke `UPDATE` adds `organizationId` and `status='active'` to
  the `WHERE` so re-revoke is idempotent and cannot transition an
  `expired` row to `revoked`.

Skipped: #6 (relay replay/tunnel-ownership) — the existing host proxy
paths don't call `maybeReplay` either, so this PR doesn't regress the
single-region behavior. Multi-region replay is a broader gap tracked
separately.
AviPeltz added a commit that referenced this pull request May 11, 2026
* feat: browser-based remote control for v2 desktop terminals

Adds an end-to-end remote control flow: a desktop user clicks Share on a
v2 terminal pane, gets a one-time URL, and anyone opening the link in a
browser sees the live terminal output and can type into it.

Pieces:
- v2_remote_control_sessions table + remote_control_session_{mode,status}
  enums, with a unique index on token_hash. Cloud row stores the SHA-256
  hash only; the host-service mints and verifies the HMAC-signed token
  end-to-end so a leaked DB row cannot grant new sessions.
- Shared protocol module (packages/shared/remote-control-protocol)
  pinning the wire format, capabilities, and limits.
- Host-service: session manager (token mint/verify, registry, expiry
  sweep), /remote-control/:sessionId WS route, attachTerminalViewer fan-
  out on TerminalSession (256 KB tail ring, output sequence, viewer
  set), and terminal.remoteControl.{mintToken,revoke,listActive} tRPC.
- Cloud tRPC: remoteControl.{create,get,revoke,listForWorkspace,
  expireStale}; mints a short user JWT for the relay POST and inserts
  the row only after the host returns a token.
- Relay: lets /hosts/:hostId/remote-control/* skip the user-JWT gate;
  the per-session HMAC validated by the host is the credential.
- Desktop UI: TerminalRemoteControlButton on v2 terminal panes (radio
  icon -> live badge dropdown with copy/stop) wired through pane
  registry. Host-service spawn now propagates HOST_SERVICE_SECRET into
  the child env so the secret derivation is stable.
- Web viewer: /agents/remote-control/[sessionId] page + RemoteTerminal
  client component (xterm.js + addon-fit, mobile-only key toolbar,
  status badge, copy/stop controls).

Out of scope: viewer-count fan-out, host->cloud heartbeat for
last_connected_at, host-driven resize push, and rebuilding the in-
memory session registry across host-service restarts (viewers see
session-not-found and the desktop user re-shares).

* style(web): match desktop terminal theme in remote-control viewer

Pulls the xterm options and color palette from the desktop's default
"dark" (Ember) theme so the browser viewer renders identical font, font
size, scrollback, ANSI colors, cursor, and selection. The chrome (header,
buttons, banners, mobile toolbar) now uses the same warm-dark surface
tones (#151110/#1a1716/#2a2827) and matches the ANSI accent colors for
status badges and the destructive Stop button.

`vtExtensions` (kittyKeyboard) and `scrollbar` are intentionally omitted
because they only exist on the desktop's xterm beta build; the stable
web release does not type them.

* fix(remote-control): address PR review findings

Resolves 11 findings from greptile + coderabbit review on the
remote-control feature:

- #1 (P1): `remoteControl.get` is now `publicProcedure` and accepts the
  raw token, hashing it for constant-time comparison against the row's
  `tokenHash`. Anonymous viewers can resolve `wsUrl` without a Superset
  session — the share link itself is the credential.
- #10 (Major): the host-side `sendInput` no longer round-trips bytes
  through a latin1 string before `pty.write` re-encodes them as UTF-8
  (which corrupted any byte ≥ 0x80). Adds `pty.writeBytes` that
  forwards a `Uint8Array` straight to the daemon.
- #2: a single `cleanup()` helper now handles `onClose` and `onError`,
  removing the viewer from the session's set, detaching the handle, and
  unsubscribing the revoke listener idempotently. Fixes a leak where
  abrupt teardown could orphan up to four `MAX_VIEWERS` slots until host
  restart.
- #8: client WebSocket payloads are validated via a zod discriminated
  union before dispatch; `resize` and `runCommand` are wrapped in
  try/catch like `input` was.
- #5: `TerminalRemoteControlButton` hydrates from
  `remoteControl.listForWorkspace` on mount and refreshes every 30s, so
  the live badge survives remounts and reflects backend revocation /
  expiry. The original `webUrl` is unrecoverable after `create` (the
  cloud only stores `tokenHash`), so Copy Link is disabled when we
  don't hold it.
- #3: handshake-time auth result is cached on the WS context; per-
  message handling just compares `expiresAt` against `now` instead of
  re-running HMAC + SHA-256 at 200/s/viewer.
- #4: the bearer token is now passed in the URL fragment
  (`#remoteControlToken=…`), not the query string. The fragment never
  reaches the server, never appears in `Referer` headers, and stays out
  of access logs and history. A new `RemoteTerminalLoader` client
  component reads `location.hash` after mount.
- #7: the web viewer writes a one-time dim hint into xterm when the
  user types in `command` mode so silent drops are explained.
- #9: oversized PTY chunks (> 256 KB in one event) now have their tail
  preserved instead of being pushed-and-immediately-shifted out of the
  ring, which would have left late-joining viewers with an empty
  snapshot.
- #11: host-side mintToken schema now `.min(MIN_TTL).max(MAX_TTL)`,
  matching `mintRemoteControlToken`'s internal clamp.
- #12: revoke `UPDATE` adds `organizationId` and `status='active'` to
  the `WHERE` so re-revoke is idempotent and cannot transition an
  `expired` row to `revoked`.

Skipped: #6 (relay replay/tunnel-ownership) — the existing host proxy
paths don't call `maybeReplay` either, so this PR doesn't regress the
single-region behavior. Multi-region replay is a broader gap tracked
separately.

* fix(remote-control): address second-round PR review

Resolves four further findings on PR #4345:

- High: redact `remoteControlToken` (and any `token` query param) from the
  relay's request logger. The viewer has to put the bearer on the WS
  upgrade URL because browser WebSockets can't carry custom headers, and
  Hono's default `logger()` would otherwise spill the raw token into Fly
  logs / Sentry breadcrumbs.

- High: when the bypass for `/hosts/:hostId/remote-control/*` skips the
  user-JWT `authMiddleware`, still run the tunnel-presence check + Fly
  `maybeReplay`. Previously a viewer landing on a relay instance that
  doesn't own the destination tunnel would get a hard failure instead
  of a `fly-replay` to the right region/instance.

- Medium: the web viewer's Stop button now calls a new public
  `remoteControl.revokeWithToken({ sessionId, token })` mutation. The
  protected `revoke` requires a Superset session, which anonymous share
  recipients don't have, so the previous wiring silently 401'd. The
  bearer token IS the credential — anyone holding it has the same
  authority as whoever they got the link from. Constant-time SHA-256
  match against `tokenHash`, then revoke + best-effort host tear-down.

- Medium: `revoke` (protected) and `listForWorkspace` now require host
  membership in addition to active org membership, matching the gate
  on `create`. Otherwise an org member who isn't on the host could
  enumerate or revoke other people's share sessions.

The shared host-revoke flow is factored into `callHostRevoke()` so both
the protected and public revoke paths use the same best-effort tear-
down.

* fix(remote-control): make anonymous viewers actually work in prod

- Move /agents/remote-control/[sessionId] out of the `(agents)` route
  group into `(public)` so it skips `getAgentsUiAccess`, and add it to
  `proxy.ts` publicRoutes so unauthenticated viewers aren't redirected
  to /sign-in before the page mounts.
- Allow the relay WebSocket origin in the prod CSP `connect-src` so
  `wss://relay…` isn't blocked once `ws:/wss:` are dropped outside dev.
- Stop swallowing host-revoke failures: both revoke paths now throw a
  TRPCError if the host call fails, so the Stop button can't report
  success while in-memory host sessions (and connected viewers) live on.
  The cloud row still flips to `revoked` first, so retries stay
  idempotent and new attaches via `get` are blocked either way.

* fix(remote-control): keep bearer tokens out of URLs and harden cloud gates

- Convert `remoteControl.get` from a query to a mutation so tRPC's
  httpBatchLink puts the bearer token in the POST body instead of the
  URL query string (which would land in access logs and Referer). The
  web viewer now calls `.mutate()`.
- `get` refuses to hand out `wsUrl`/`routingKey` for non-active rows
  (revoked/expired). Also promotes active rows past `expiresAt` to
  `expired` even if the sweep hasn't run, so a just-expired share
  can't slip through. `SessionMeta.wsUrl` is now `string | null` and
  the WS-connect effect gates on it.
- Add `timeoutMs: 5000` to the mintToken relayMutation in `create`
  so a stuck host can't pin the Share button in "Starting…".
- Wrap the cloud `INSERT` in try/catch; on failure best-effort call
  `callHostRevoke` so the host-side minted token isn't orphaned and
  invisible to `listForWorkspace`/`revoke` until the host TTL sweep.

* chore(db): renumber remote-control migration to 0050 after main merge

Main shipped its own 0048 + 0049 while this branch had the original
0048_add_v2_remote_control_sessions. Regenerated via drizzle-kit
generate; same DDL, new slot.

* feat(remote-control): gate Share button on PostHog flag + add Open-in-browser

- New \`WEB_REMOTE_CONTROL_ACCESS\` (\`web-remote-control-access\`) feature
  flag controls who sees the Share button on v2 desktop terminal panes.
  Evaluated against the sharer's user id; the per-session HMAC stays the
  credential for anyone with the link, so this only gates session
  creation.
- TerminalRemoteControlButton returns null when the flag is off and skips
  the 30s cloud-hydrate poll for that user, so non-cohort users don't
  pay a tRPC round-trip every interval.
- Adds an "Open in browser" item to the live-badge dropdown that uses
  \`window.open(url, "_blank")\` (Electron routes it to the system
  browser), so the sharer can verify the link without leaving the app
  to paste it.

* refactor(remote-control): one-folder-per-component layout for web viewer

- Promote `RemoteTerminalLoader` to a sibling folder under `[sessionId]/
  components/RemoteTerminalLoader/` so `page.tsx`'s import resolves
  through a barrel that actually points at it (used to import the
  loader via the `RemoteTerminal` barrel, which was misleading).
- Extract inline `MobileToolbar` from `RemoteTerminal.tsx` into nested
  `RemoteTerminal/components/MobileToolbar/MobileToolbar.tsx` per the
  project's one-folder-per-component convention. No behavior change.

* fix(remote-control): close enumeration, schema leak, observability gaps

- Collapse `get` + `revokeWithToken` into a single generic 401
  ("Invalid remote control session or token") whether the row is
  missing or the token is wrong. Always runs the constant-time
  compare — against the row's tokenHash when present, otherwise
  against DUMMY_TOKEN_HASH — so timing and response both equalize
  and sessionIds can't be probed via these endpoints.
- Wrap the raw drizzle/pg error in `create`'s INSERT-failure path
  with TRPCError so pg constraint / column names don't get echoed
  back to clients through the default tRPC serializer.
- Upgrade the orphan-cleanup swallow from `console.warn` to a
  structured `console.error("[remote-control:orphan-host-session]",
  { sessionId, hostId, organizationId, insertError, revokeError })`
  so log scrapers can alert and a future Sentry
  `captureConsoleIntegration` picks it up automatically.

* docs(remote-control): plan for remaining work

* fix(remote-control): debounce viewer resize broadcasts via requestAnimationFrame

ResizeObserver can fire at refresh-rate (~60Hz) during a window-drag,
which immediately trips the host's REMOTE_CONTROL_RESIZE_RATE_PER_SEC
= 10 rate limit and surfaces a spurious "rate-limited" banner in the
viewer during normal use. Coalesce to one fit+broadcast per animation
frame and cancel any pending frame in the cleanup so we don't
fit+send after the WS is gone.

* fix(remote-control): trailing-debounce resize broadcasts to fix rate-limit banner

The RAF coalescing from the previous commit didn't actually help —
ResizeObserver already fires once per layout, so RAF was a no-op for
sustained window-drag, and the host's 10/s bucket still drained in
~166ms and tripped the "rate-limited" error.

Switch to a trailing 200ms debounce: keep calling `fit()` per event so
local rendering stays responsive, but only fire the host-side resize
message once after the user stops dragging. 5 Hz worst case, well
under the host's 10/s cap.

* ci(sherif): allow @xterm/{xterm,addon-fit} version split

Desktop's terminal-runtime uses the @XTerm beta 6.x track for
kittyKeyboard + scrollbar options; the new web remote-control viewer
uses stable 5.x because the beta is too churny for a fresh feature.
The split is intentional and documented in the viewer's source, so
tell Sherif to ignore those two deps rather than force-aligning to
either track.
Kitenite added a commit that referenced this pull request May 12, 2026
Per AGENTS rule #6 (reference docs live in <app>/docs/). Moves the
HOST_SERVICE_* and V2_WORKSPACE_* docs; fixes the few path references in
plans/ and a code comment, and the GIT_REFS.md relative link inside
V2_WORKSPACE_CREATION.md.
Kitenite added a commit that referenced this pull request May 12, 2026
* feat(desktop): folders + tree view modes for v2 changes sidebar

Adds v1's two grouping modes to the v2 changes sidebar:
- Folders mode (new default): files grouped one level deep by parent folder,
  reusing FileRow; root files under a "Root Path" header.
- Tree mode: one PierreFileTree per category section — hierarchy, virtualization,
  status tints, icons from Pierre; right-click parity with FileRow.

View mode persists per workspace via sidebarState.changesViewMode; toggled from
ChangesHeader. Extracts the Pierre row click-policy into a shared
usePierreRowClickPolicy hook and lifts RowContextMenu up to
WorkspaceSidebar/components/PierreRowContextMenu (used by both tabs).

* fix(desktop): give changes tree view an explicit height so it renders

Pierre's FileTree host is `height: 100%; overflow: hidden` when virtualized.
Inside a changes section's auto-height container that collapses to 0, so the
tree was invisible. Size each tree explicitly to its content: derive the
expanded row count from the path list and recompute via model.subscribe when
folders collapse/expand.

* feat(desktop): changes tree — hover actions, folder menus, count badge, selection sync

- Hover overlay on file rows: Discard (unstaged) + more-actions ⌄ dropdown,
  matching FileRow. Anchors a light-DOM overlay over Pierre's shadow-root rows.
- Right-click on directory rows: Open in Editor + copy-path actions.
- Directory rows show a file-count decoration.
- Echo the diff pane's open file into the tree's selection when it belongs to
  the section (with a feedback-loop guard).
- Hoist the discard confirm dialog to ChangesTreeView — Pierre tears down
  renderContextMenu output on close, which would unmount a dialog inside it.

Not done (out of reach in v2): per-file Stage/Unstage (host-service git API
has no path-scoped staging), per-folder bulk actions, drag-to-copy path
(Pierre owns row DOM), colored +N/−N (Pierre row decoration is text/icon only).

* feat(desktop): add collapse-all/expand-all toolbar under each changes section

A thin action row below each section header (both folders and tree modes),
mirroring FilesTab's header button strip. Folders mode toggles the folder
groups; tree mode collapses/expands the Pierre tree's directories (which
resizes the section via the existing row-count subscription).

* refactor(desktop): single changes toolbar row; drop header background + per-section toolbars

- New ChangesToolbar row beneath the changes header: folders/tree toggle on
  the left (moved out of the header), expand-all / collapse-all on the right.
- Removed the per-section collapse/expand toolbars; expand/collapse-all now
  fans out to every section via a fold-signal prop (folder groups in folders
  mode, Pierre tree directories in tree mode).
- Dropped the muted background tint from the changes header.

* refactor(desktop): single collapse/expand-all toggle in changes toolbar

* fix(desktop): size changes tree to Pierre's real content height

The previous estimate (dirs + files) over-counted badly for deep monorepo
paths because Pierre flattens single-child directory chains into one row, so
the tree host ended up far taller than its content (big gap below the rows).
Read the content height Pierre writes to the virtualized list's inline style
instead, re-reading after each model mutation.

* style(desktop): right-align view toggle next to collapse button; drop header/toolbar divider

* style(desktop): add bottom padding to changes toolbar row

* style(desktop): move changes filter dropdown into the toolbar row

* style(desktop): merge changes stats into the toolbar row next to the filter

* style(desktop): drop bottom border from changes toolbar row

* Revert "style(desktop): drop bottom border from changes toolbar row"

* chore(desktop): trim a restating comment in ViewModeToggle

* refactor(desktop): share loadFallthroughIcons util across sidebar tabs

* refactor(desktop): consolidate file-icon system into renderer/lib/fileIcons

Move FileIcon / getFileIcon / resolveFileIconAssetUrl / loadFallthroughIcons
and the manifest typing into one shared module so the v2 sidebar trees, the
diff/pane chrome, and the chat/mention surfaces all draw from the same place
(v1 FilesView/utils now just re-exports it).

Also give unrecognized file types a real fallback icon in the Pierre trees:
loadFallthroughIcons now adds the Material default-file icon to the sprite and
remaps Pierre's generic `file` slot to it, matching what the non-tree FileIcon
surfaces already do via manifest.defaultIcon.

* fix(desktop): give changes folder headers a working path tooltip

The native `title` attribute doesn't render reliably in the renderer; use
the shadcn Tooltip (same component FileRow uses for its hover hint) so the
full folder path shows on hover.

* refactor(desktop): extract shared Pierre-tree module + decompose FilesTab

- New renderer/lib/pierreTree: createPierreTreeStyle (the ~55-line --trees-*
  CSS-var map, parameterized by row height / indent / search chrome) and the
  FILE_STATUS_TO_PIERRE git-status mapping — both were duplicated verbatim in
  FilesTab and ChangesTreeView.
- FilesTab (722 → ~290 lines): pull the filesystem actions (create / rename /
  delete / reveal / collapse-all + the bridge bookkeeping) into a
  useFilesTabActions hook; move buildPierreGitStatus and the creation-path
  helpers into utils/; lift the header icon button into its own component;
  add a local constants.ts (FILE_EXPLORER_ROW_HEIGHT/INDENT/OVERSCAN), which
  also drops FilesTab's last import from the sunsetting v1 FilesView code.
- ChangesTreeView: use the shared style + status map; extract the content-
  height measurement into a useMeasuredTreeHeight hook and buildTreeShape into
  utils/.

No behavior change.

* refactor(desktop): extract useFallthroughIcons hook

The setIcons-after-load effect was copy-pasted in FilesTab and ChangesTreeView;
move it into renderer/lib/fileIcons as useFallthroughIcons(model).

* refactor(desktop): share stripTrailingSlash via renderer/lib/pierreTree

It was defined in FilesTab's treePath utils and duplicated inline in
ChangesTreeView; move it to renderer/lib/pierreTree (the Pierre tree's
directory-marker convention) and re-export it from treePath.

* docs(desktop): move architecture docs into apps/desktop/docs/

Per AGENTS rule #6 (reference docs live in <app>/docs/). Moves the
HOST_SERVICE_* and V2_WORKSPACE_* docs; fixes the few path references in
plans/ and a code comment, and the GIT_REFS.md relative link inside
V2_WORKSPACE_CREATION.md.

* fix(desktop): use folderPath directly as the changes folder-group key

Drops the "__root__" sentinel key, which could collide with a real
top-level folder named "__root__"; folderPath ("" for root) is already
the unique per-group discriminator.

* fix(desktop): repair ChangesFoldersView JSX broken by bad comment placement

The previous commit put a JSX `{/* */}` comment directly after `return (`,
which biome's formatter then mangled into invalid syntax. Move it to a plain
`//` comment above the return.

* fix(desktop): drop changes tree hover overlay on scroll
justincrich added a commit to justincrich/superset that referenced this pull request May 22, 2026
…mes (v1.6.0)

Adds shell-level navigation as a sixth functional group with 6 UCs covering
the Chat bottom-nav tab, sessions list sectioned by workspace with
collapse/expand, host-picker bottom sheet (Slack-style one-host-at-a-time),
FAB + workspace-picker for new sessions, push-notification deep-link
routing with host alignment, and empty-state handling.

Canonical ASCII wireframes for all five surfaces in 09-uc-nav.md:
- Sessions list (Chat tab default landing)
- Host-picker bottom sheet
- New-chat workspace picker
- Deep-link routing flow
- Empty states (no-hosts / no-workspaces / no-sessions)

Resolves Technical Sub-Decision ParadevAI#6 (host selection and workspace->host
resolution): hostId comes from v2_workspaces.hostId on Electric-synced
rows, selected host persisted locally per user+org. No new tRPC procedures
or cloud-router additions required for the NAV surface — all four data
sources (v2_hosts, v2_users_hosts, v2_workspaces, chat_sessions) are
already exposed by apps/electric-proxy/src/where.ts.

Numbering cleanup along the way:
- ADD 09-uc-nav.md
- 09-team-contributions.md -> 10-
- 10-technical-requirements/ -> 11-
- 11-component-organization-addendum.md -> 12-
- 11-testing-strategy.md -> 13- (fixes the existing 11/11 collision)

Cross-references updated in 01-scope.md, 11-technical-requirements/
04-dependencies.md, 11-technical-requirements/05-ui-infrastructure.md,
11-technical-requirements/06-open-sub-decisions.md. README Document
Index, Quick Stats (32 UCs / 6 groups / 12 components), and version
history reflect the changes. Version bumped 1.5.0 -> 1.6.0.

Design grounded in:
- Saddlepaddle review feedback on PR ParadevAI#4828 comment r3285215736
- Brainstorming dialog with the PRD owner: primary task = "check on
  in-flight session" (notifications-first), list shape = sectioned by
  workspace, host picker = header chip + bottom sheet, workspace tap =
  collapse/expand only (workspace details remain in existing Workspaces
  tab).
justincrich added a commit to justincrich/superset that referenced this pull request May 22, 2026
Codebase-verified review found 5 critical issues and 4 medium issues.
All fixes applied against real file:line evidence.

Critical fixes:
- UC-RENDER-02: desktop uses polling at ~4 FPS, not character-drip
- Transport framing: mobile relay-HTTP is deliberate adaptation of
  desktop IPC, not a mirror
- Custom slash command awareness: ACs for rendering host-discovered
  custom commands in @rn-primitives/popover
- Host selection sub-decision ParadevAI#6: workspace→host resolution gap
- previewSlashCommand: added to API table + UC-COMP-01 ACs

Medium fixes:
- getDisplayState added to API table
- Optimistic update ACs for UC-PAUSE-01/02/03
- Line refs corrected, expo-notifications SDK 55→56

Also includes component tree restructure into AGENTS.md co-location
tree format with screen/lib directory plans.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
justincrich added a commit to justincrich/superset that referenced this pull request May 22, 2026
…mes (v1.6.0)

Adds shell-level navigation as a sixth functional group with 6 UCs covering
the Chat bottom-nav tab, sessions list sectioned by workspace with
collapse/expand, host-picker bottom sheet (Slack-style one-host-at-a-time),
FAB + workspace-picker for new sessions, push-notification deep-link
routing with host alignment, and empty-state handling.

Canonical ASCII wireframes for all five surfaces in 09-uc-nav.md:
- Sessions list (Chat tab default landing)
- Host-picker bottom sheet
- New-chat workspace picker
- Deep-link routing flow
- Empty states (no-hosts / no-workspaces / no-sessions)

Resolves Technical Sub-Decision ParadevAI#6 (host selection and workspace->host
resolution): hostId comes from v2_workspaces.hostId on Electric-synced
rows, selected host persisted locally per user+org. No new tRPC procedures
or cloud-router additions required for the NAV surface — all four data
sources (v2_hosts, v2_users_hosts, v2_workspaces, chat_sessions) are
already exposed by apps/electric-proxy/src/where.ts.

Numbering cleanup along the way:
- ADD 09-uc-nav.md
- 09-team-contributions.md -> 10-
- 10-technical-requirements/ -> 11-
- 11-component-organization-addendum.md -> 12-
- 11-testing-strategy.md -> 13- (fixes the existing 11/11 collision)

Cross-references updated in 01-scope.md, 11-technical-requirements/
04-dependencies.md, 11-technical-requirements/05-ui-infrastructure.md,
11-technical-requirements/06-open-sub-decisions.md. README Document
Index, Quick Stats (32 UCs / 6 groups / 12 components), and version
history reflect the changes. Version bumped 1.5.0 -> 1.6.0.

Design grounded in:
- Saddlepaddle review feedback on PR ParadevAI#4828 comment r3285215736
- Brainstorming dialog with the PRD owner: primary task = "check on
  in-flight session" (notifications-first), list shape = sectioned by
  workspace, host picker = header chip + bottom sheet, workspace tap =
  collapse/expand only (workspace details remain in existing Workspaces
  tab).
justincrich added a commit to justincrich/superset that referenced this pull request May 22, 2026
Flip the mobile sessions-list mental model from host-first to
project-first. Sessions list becomes a flat, recency-sorted FlashList
with workspace shown as inline row metadata (no sectioning, no sticky
headers, no per-section pagination). Filter UI moves to a bottom sheet
(workspace + status) triggered from a ⚙ button next to the search bar;
applied filters render as removable chip tags below the search bar.
Tunnel lifecycle becomes lazy — opened on chat-route mount via
`workspace.hostId`, dropped on unmount.

UC cascade
  - Retire UC-NAV-02 (sticky workspace sections + per-section pagination)
  - Retire UC-NAV-03 (header host picker)
  - Retarget UC-NAV-01 (new header composition: project chip + search + ⚙)
  - Retarget UC-NAV-04 (FAB workspace picker scoped to project, rows show
    `branch · host` across all hosts)
  - Retarget UC-NAV-05 (push deep-link aligns project; readiness gate
    awaits v2_workspaces sync, falls back to tRPC chat.getSnapshot on
    cold-launch race; tunnel opens lazily on chat-route mount)
  - Retarget UC-NAV-06 (5 empty states including new filters-no-match)
  - Retarget UC-NAV-07 (search scope = project, host as row metadata only)
  - Add UC-NAV-08 (filter sessions by workspace + status; applied chip
    tags + badge count)

Component changes
  - Rename SelectedHostProvider → SelectedProjectProvider, with one-time
    migration that drops legacy selectedHostId from expo-secure-store
    and seeds selectedProjectId via most-recent-activity / alphabetical-
    first fallback. Also handles "selected project no longer accessible"
    with a re-run + toast.
  - Delete HostPickerSheet.
  - Add ProjectPickerSheet (only renders when org has ≥2 projects),
    SessionFilterSheet (workspace + status multi-select, no host axis,
    no date axis), AppliedFilterTags (horizontally-scrollable chip row
    with stale-chip cleanup on Electric tombstones).
  - Add useChatTunnel hook with cross-remount de-dupe (single in-flight
    per hostId), background/foreground lifecycle, and skeleton/retry UI
    state shape.
  - SessionsListScreen state shape becomes
    `activeFilters: { workspaceIds[], statuses[] }` + `searchQuery: string`,
    both in-memory only.
  - Row truncation order locked: title → branch → host → time.

Backend invariants preserved
  No new tRPC procedures. No Electric shape changes. No relay protocol
  changes. The chat_sessions shape stays org-scoped at
  apps/electric-proxy/src/where.ts:136-137 and all filtering happens
  client-side via useLiveQuery joins to v2_workspaces (cache-first per
  AGENTS.md TanStack DB rule). Push payload remains
  `{ sessionId, workspaceId, hostId, kind }` — mobile derives projectId
  from the synced workspace row.

Files
  README.md ........................ v1.8.0 → v2.0.0, full changelog row
  01-scope.md ...................... NAV in-scope items rewritten;
                                     new [NOT SUPPORTED] / [DEFERRED]
                                     items for sectioning, sticky
                                     headers, host picker, per-section
                                     pagination, host filter axis,
                                     date filter axis, workspace
                                     creation on mobile
  03-functional-groups.md .......... NAV description rewritten;
                                     UC count 7 → 6, total 33 → 32
  09-uc-nav.md ..................... full rewrite (flat-list wireframe,
                                     project picker wireframe, filter
                                     sheet + applied-tag wireframe,
                                     new-chat picker wireframe, deep-
                                     link flow, 5 empty-state wireframes,
                                     all new ACs)
  11-technical-requirements/
    00-system-components.md ........ Host-picker sheet removed,
                                     SelectedHost→SelectedProject
                                     renamed, Project-picker sheet +
                                     Session-filter sheet +
                                     useChatTunnel rows added,
                                     Sessions list screen + Deep-link
                                     handler rows rewritten, backend
                                     invariant note added
    02-api-design.md ............... Electric collections table
                                     rewritten; v2_hosts/v2_users_hosts
                                     rows removed, v2_projects added;
                                     client-side scoping section
                                     rewritten for project-first model
    04-dependencies.md ............. expo-router route list updated
                                     (host-picker route removed,
                                     project-picker + filter routes
                                     added)
    05-ui-infrastructure.md ........ SessionsListScreen subcomponents
                                     replaced, providers/hooks list
                                     rewritten, TestIDs documented
    06-open-sub-decisions.md ....... Sub-Decision ParadevAI#6 re-resolved with
                                     the project-first model,
                                     superseding v1.6.0 host-first

Adversarial validation
  Pre-write review by react-native-ui-planner, frontend-designer, and
  node-planner specialists (parallel dispatch via Task tool). All
  surfaced concerns folded in: tunnel-handshake skeleton + timeout
  error, legacy selectedHostId migration, deleted-project fallback,
  row truncation order, FlashList tuning, accessibility states,
  deep-link readiness gate, useChatTunnel dedupe, ProjectPickerSheet
  cache-first counts.

Downstream
  ROADMAP.md in plans/chat-mobile-plan/ROADMAP.md still references the
  retired v1.x NAV components (HostPickerSheet, WorkspaceSection,
  LoadMorePill, HostChip, MOB-INFRA-006 SelectedHostProvider). It is
  generated by /kb-sprint-plan and should be regenerated with
  /kb-sprint-plan --delta-replan to align the implementation roadmap
  with the v2.0.0 spec. ROADMAP.md is intentionally NOT touched in this
  commit — that is the sprint planner's job, not the PRD's.
mikigraf added a commit to mikigraf/superset that referenced this pull request Aug 27, 2026
- auth: hook token now REQUIRED once registered (reject missing/wrong),
  not just wrong — closes the token-less spoofing bypass (CWE-287)
- config: agentConfig (host credential mount) is machine-local-only and
  defaults OFF, so a cloned repo can't mount host agent creds (CWE-829)
- git-sync: guard reset --mixed with the checked-out branch so a manual
  checkout / detached HEAD can't advance the wrong branch
- git-bootstrap: use the last-written sha file as the completion
  sentinel and rebuild partial state, so an interrupted bootstrap can't
  report success with the .git isolation mask missing
- container-manager: rename containers in place on workspace/branch
  rename (docker rename preserves live exec sessions) instead of rm -f
- container start already-running fix retained from prior batch

Covers CodeRabbit items ParadevAI#6, ParadevAI#9, ParadevAI#10, ParadevAI#11, ParadevAI#12.

This branch was successfully deployed

1 active deployment
Preview — 9d248eb9 Deployed Oct 28, 2025 by vercel[bot]
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.

1 participant