SageOx  ·  KB pages & widgets — V2 (senior designer pass · post Apple+Linear+Tufte review)  ·  ← V1
Mode

1. KB list

/kb apps/web/app/(protected)/kb/page.tsx

Landing for the KB experience. Personal KB at top, then Profile placeholder, then team-grant KBs grouped by team. Toggle between list and Constellation views.

sageox.ai/kb

Your Knowledge Bubbles

Yours, your teams', the ones you've been invited to.

+ New bubble
Personal · 1 of 1

Ryan's personal

Yours alone · 412 notes · 14 chats
Personal
Provisioned May 4, 2026 · last active 2m ago

Your public profile

Optional — a public face for your work. Not provisioned.
Profile
Create profile bubble →
SageOx Internal · 4 bubbles

SageOx Internal

Team-context · 7 members · 38 discussions
Team
Active · 14 signals today

sageox/sageox-app

Repo · 7 members · last commit 8m ago
Repo
Active · 22 signals today

sageox/scribe

Repo · 4 members · last commit 4h ago
Repo
Active · 4 signals today

#cast-team

Channel · distilled from Slack · 6h ago
Channel
Active · 2 signals today

Q3 planning

Custom · 5 members · created Apr 2
Custom
Active · 1 signal today

provisioning…

sageox/new-repo · queued 4m ago
Retry →

2. Create a new bubble

/kb/new

Custom or repo KB. Personal & channel KBs are not user-created (auto-provisioned). Team-context KBs auto-provision with the team itself, so they're not in the picker either. Fields below the Kind selector adapt based on what's chosen — Custom asks for an owner (yourself or a team you're in); Repo asks for a GitHub account + repo and inherits ownership from the team that owns the repo.

sageox.ai/kb/new

Create a bubble

A bubble is a private space for related thinking — notes, discussions, files, agent memory.

Kind
Why no "Team" or "Personal"? Team-context bubbles auto-provision when a team is created (one per team — you can't make a second). Personal & profile bubbles auto-provision per user at signup. Channel bubbles are distilled, not created.
Choose a short, descriptive name.
Will be addressable as #q3-planning
If you assign to a team, you become this bubble's admin. The team's other members get Member role.
If Kind = Repo, the form swaps these fields for a GitHub account picker + repo selector, and ownership auto-derives from the team that owns the repo. The team-grant happens automatically.

3. Team page — the team entity

/team/[team_id] distinct from any single KB

The team entity is not the same as any of its KBs. The team page is the rollup of everything the team owns: members, all bubbles, repos, billing, settings. Its handle is @sageox-internal. The team-context bubble (#sageox-internal) is just one of those owned KBs — visible below in the constellation widget alongside the team's repo bubbles, custom bubbles, and channel bubbles.

sageox.ai/team/sageox-internal
Teams/SageOx Internal
SO

SageOx Internal

Team Owner
@sageox-internal · 7 members · 9 bubbles · 9 repos

This week

Bubbles
9
+1 this week
Signals
214
+38% vs last week
Discussions
12
3 active right now
Coworkers active
7
All members touched something

Active work · across all 9 bubbles

Restarting PR #231 (MoQ qmux wrap). moq_embed native tests: 13/13 green.
#sageox/scribe 4m ago
hover, pinned
Sketching Q3 KB roadmap — three workstreams: connect widgets, onboarding, attribution rework.
#sageox-internal 8m ago
Production cast-renderer dropping audio on BRAVIA — pinning to last-good #1218.
#sageox/cast 6m ago

Recent discussions · across all 9 bubbles

🎙
Q3 architecture review — KB chat workspaces
Ryan, Mihira, Galex · live · 12 min so far · #sageox-internal
now
🎙
BRAVIA HLS audio track investigation
Ryan + 2 · 4 follow-up chats · #sageox/cast
22m ago
🎙
AI Council Conference Debrief & Testing Sprint
Speaker 1, +3 · 32:51 · #sageox-internal
7d ago
Distinction: @sageox-internal is the team entity — members, billing, the rollup of everything the team owns. #sageox-internal is the team-context KB — one specific bubble holding the team's shared thinking. Same root slug, different prefix, different surface. Other team-owned bubbles (#sageox/scribe, #cast-team, #q3-planning) sit alongside it — none are "the team."
for the engineer building /team/[id]

Mental model. A team page is the rollup of an organization's KBs, members, and repos. It is not any single KB. The team-context KB (kb_type='team', aliased_team_id=<team.id>) is one of N bubbles owned by the team; the team page lists it alongside repos, channel KBs, and custom KBs.

Stats tile semantics. "Bubbles" = count of KBs where the team is owner or grantee. "Signals" = murmur count across all those KBs in last 7 days. "Discussions" = recording count + live-now subset. "Coworkers active" = users (incl. AI coworkers) who touched anything in the team this week. These need a single endpoint (GET /api/v1/teams/:team_id/stats?window=7d) returning all four — don't fan out from the client.

Active-work feed. Cross-bubble — aggregates murmurs from every KB the team owns. The murmur API today (/api/v1/teams/:team_id/murmurs) already fans across up to 20 repos; extend that to include all team-owned KBs.

Right rail. The compact bubble constellation in the right rail uses the existing KBConstellation component (apps/web/src/components/kb/KBConstellation.tsx) with the compact variant — see §12f for the encoding.

Anti-patterns to avoid.

  • Don't conflate "team settings" with "team-context-KB settings." They live at separate routes (/team/[id]/settings/* vs /kb/[kb_id]/settings) and store different data.
  • Don't show kb_type='team' bubbles in the  Create  flow — they auto-provision.

Files (best guess). New route apps/web/app/(protected)/team/[team_id]/page.tsx (replace existing if needed). New stats endpoint apps/api-go/internal/handlers/teams/stats.go. Existing component reuse: MurmurFeed, KBConstellation.

3b. Team Settings → Bubbles NEW

/team/[team_id]/settings/bubbles team admin only

Doesn't exist in code or in V1. Without it, a team admin manages each bubble individually — a 20-tab afternoon for a team of 9 bubbles. This page makes ownership transfer, audit, and deletion tractable.

sageox.ai/team/sageox-internal/settings/bubbles

Bubbles

9 bubbles · 1 team-context · 7 repo · 1 custom · 1 channel

+ New bubble
BubbleKindMembersLast activityOwnerStatus
SO
SageOx Internal
#sageox-internal
Team 7
8m ago
RSRyan
Active
SA
sageox-app
#sageox/sageox-app
Repo 7
2m ago
ABAjit
Active
CA
cast
#sageox/cast
Repo 5
12m ago
RSRyan
⚠ critical signal
SC
scribe
#sageox/scribe
Repo 4
4h ago
ABAjit
Active
#
cast-team
#cast-team
Channel derived
6h ago
— (derived)
Active
Q3
Q3 planning
#q3-planning
Custom 5
1d ago
MRMihira
Active
Row actions menu (): Transfer ownership · Change visibility · Audit grants · Delete bubble. Bulk action bar appears on selection: Change visibility · Audit grants · Export audit log · Delete. Delete is permanent (no archive today, per the data model).
for the engineer building Team Settings → Bubbles

Why this surface matters. Without it, a team admin manages each bubble individually — a 20-tab afternoon for a team of 9 bubbles. This page makes ownership transfer, audit, and deletion tractable.

Auth. Admin-only. Use RequireTeamRole('admin') middleware. Non-admins should not see the route in their team-settings sub-nav (filter EntitySettingsNav.tsx).

Table columns. Bubble (avatar + name + slug) · Kind · Members count (with mini-stack of 3) · Last activity (with act-pulse dot) · Owner (current human accountable) · Status · Actions ( menu).

Row actions.

  • Transfer ownership — change kb.owner_user_id to another team member. Modal confirms the transfer; new owner is notified.
  • Change visibility — toggle kb_team_grants visibility (private to grantees vs discoverable to team). Confirm modal.
  • Audit grants — opens a right-side sheet showing who has access, role, granted-by, granted-at, last-active. Sortable, exportable as CSV.
  • Delete bubble — permanent, confirmation modal with name-typing required (like GitHub repo delete). No archive — per the data model today.

Bulk operations. Selection checkbox per row; bulk-action bar appears at the bottom (slide-up, fade-in 150ms — single property). Bulk actions: Change visibility, Audit, Export audit log, Delete.

Empty state. "This team has no bubbles yet. The team-context bubble auto-provisions when [conditions]. Create a custom or repo bubble →" — uses the "doesn't exist yet" pattern (§12e+).

Filters. Kind · Visibility · Status · Owner — server-side filtering. Persist last-used filter in localStorage per (user, team).

Files (best guess). Route: apps/web/app/(protected)/team/[team_id]/settings/bubbles/page.tsx. Server endpoints: GET /api/v1/teams/:team_id/kbs, POST /api/v1/kb/:kb_id/transfer, PATCH /api/v1/kb/:kb_id/visibility, DELETE /api/v1/kb/:kb_id. Audit-grants sheet uses the existing kb_team_grants table.

3. Overview — Personal KB

/kb/[kb_id] kb_type = "personal"

Auto-provisioned per user. Structurally unshareable — no Members tab, no Connect-Slack widget. isPersonalKB(kb) shows the privacy banner.

sageox.ai/kb/kb_2c6q9...
Bubbles/Ryan's personal
R

Ryan's personal

🔒 Personal Owner
#ryan-personal · private to @ryan · provisioned May 4, 2026

What's in here

412 notes · 14 chats · 2 recordings
Last activity 2 minutes ago
View all →

Active work

Drafting next-quarter goals doc — pulled in last three retros + the Mihira/Ryan 1:1 transcript.
12m agoThu, May 15, 2026 · 14:18 PT
2026-05-15T21:18:04Z
notes/2026-q3-goals.md

4. Overview — Team-context KB

/kb/[kb_id] kb_type = "team"

A team entity (with its members and repos) is not the same as its team-context KB. The team-context KB is one of N bubbles a team may eventually have. aliased_team_id gives the 1:1 mapping today; the schema is structurally 1:N.

sageox.ai/kb/kb_team_7xv2...
SO

SageOx Internal

Team Admin
#sageox-internal · part of @#sageox-internal · part of @sageox-internal · 7 members · 9 bubbles
Getting set up · 1 of 4 done for you
Tracked per-user · last updated 2m ago
Bubble provisioned
2Connect your tools
3Have your first discussion
4Invite teammates

Connect

Connect your coding agent (CLI)

Run from your terminal. Your agent gets this bubble's context and can write back to it.

ox init

Connect Claude, Cursor, ChatGPT · MCP

One server URL per user. Once connected, your agent can call ListTeams / Search across every bubble you have access to.

https://api.sageox.ai/api/v1/mcp
#

Connect a Slack channel

Pipe a Slack channel into this bubble. Distilled discussions appear in the feed below.

+ Link a channel

Active work · across this team's bubbles

Restarting PR #231 (MoQ qmux wrap). Merged origin/main into ajit/moq-qmux-wrap (clean). moq_embed native tests: conformance 13/13 green.
#sageox/scribe 4m agoThu, May 15, 2026 · 14:26 PT
2026-05-15T21:26:11Z
moq_embed/native_test.cc moq_embed/pubsub_test.go +2
Fixing device_register missing X-Device-ID header (firmware 0.4.x). Patched device_api.cpp + esp_http_client_impl.cpp; testing on hardware.
#sageox/scribe 20h agoWed, May 14, 2026 · 18:42 PT
2026-05-15T01:42:13Z
device_api.cpp esp_http_client_impl.cpp
hover state, pinned
Sketching Q3 KB roadmap — three workstreams: (1) connect widgets, (2) onboarding stepper, (3) attribution rework on murmurs. Linking to ADR-030 and the in-flight discussion with Galex.
👥 Team 8m agoThu, May 15, 2026 · 14:22 PT
2026-05-15T21:22:48Z
Production cast-renderer dropping audio on BRAVIA receivers — pinning to last-good build #1218 while investigating fMP4 track.
#sageox/cast 6m agoThu, May 15, 2026 · 14:24 PT
2026-05-15T21:24:01Z
apps/cast-renderer/main.go
Renaming "shadow repo" → "ledger" across docs and CLI strings. Please refer to the new term going forward.
👥 Team 12m agoThu, May 15, 2026 · 14:18 PT
2026-05-15T21:18:33Z
Background heartbeat: re-indexing Q3 planning bubble after merge of customer-research notes.
📖 Q3 planning 2h agoThu, May 15, 2026 · 12:31 PT
2026-05-15T19:31:08Z
Distilled 18 messages from #cast-team — three open questions about BRAVIA HLS audio track, one decision recorded.
# cast-team 14m agoThu, May 15, 2026 · 14:16 PT
2026-05-15T21:16:55Z
7 of 23 visible · older items fade as they age See all activity →

Recent discussions

🎙
Q3 architecture review — KB chat workspaces
Ryan, Mihira, Galex · live · 12 min so far
now
🎙
Recording for SageOx Internal 2026-05-15
Speaker 1, Speaker 2, +1 · 47:09 · 3 follow-up chats
4d ago
🎙
AI Council Conference Debrief & Testing Sprint
Speaker 1, +3 · 32:51
7d ago
🎙
Knowledge Bubble Architecture & Privacy Policies
Ryan, Galex, +1 · 51:30
12d ago
Mental model: The team entity (members, billing, governance) is distinct from its team-context KB (one shared bubble for team-wide thinking). A team also owns repo KBs and may have custom KBs — the team is the entity, the bubbles are its KBs. Captured in aliased_team_id on the kb table; design substrate in ADR-030.
for the engineer building the team-context KB overview

The marquee surface. This is the most-visited KB page. Every design move here propagates to other kb_type variants.

Header. Bubble avatar (36×36, flat fill per kb_type with monogram) + plain-language title (20px, weight 600, letter-spacing: -0.015em, line-height: 1.2) + KBTypeBadge (small, demoted to slug line in V2) + RoleBadge. Slug line is 12.5px text-muted: #slug · part of @team · members · bubbles. CTA buttons right-aligned; one Copper CTA per view ("Have a discussion" or "Dictate memo" for personal). The avatar must be baseline-aligned with the title — V1 had it top-aligned which made the header read taller than it is.

Tabs. Overview · Members · Settings. Members only when kbAllowsMembers(kb.kb_type) (false for personal/profile). Settings only when viewerRole === 'admin'. Underline indicator: 2px sage-500 at bottom of the active tab. Transition the underline on tab change via a single shared pseudo-element that translateX()'s; don't fade the border-bottom independently per tab — that reads as jump.

Onboarding stepper. Horizontal compact variant. Per-user state stored as kb.metadata.viewer_onboarding_completed_at_<user_id>. Hover any step to see the data-tip explanation. The current step's tip is pinned-open via .show-tip class so first-time users get context without hover. Stepper dismisses via the "Dismiss" link; persist dismissal per (user, kb) in kb.metadata.

Connect widgets. Three cards: CLI (ox init), MCP (single per-user /api/v1/mcp endpoint — explain the ListTeams/Search tool model in the description), Slack channel (link via existing LinkChannelDialog). Adaptive — personal bubbles hide the Slack card; repo bubbles hide it too. Each card: icon tile + plain-language title + 1-line description + snippet/CTA + footer status. One CTA per card. Snippet uses the .copy-ic icon (no "Copy" text label).

Active work (Murmurs feed). Fixed-height container (max-height: 388px) with .decay class for opacity fade. See-more footer with gradient fade-in. Cross-bubble on this page — aggregates from every KB the team owns. Each row: 3-column grid (dot + content + meta), principal-first attribution. See §12i for the full row spec.

Right rail. Members (with avatar stack), Linked repos. Each section can become its own contextual sidebar item in the future. Avoid duplicating the sidebar's contextual section here.

Files. Page: apps/web/app/(protected)/kb/[kb_id]/page.tsx (refactor the existing). Sidebar morphing: apps/web/src/components/layout/nav/LeftNav.tsx + new useContextualNav() hook driven by usePathname().

5. Overview — Repo KB (ledger)

/kb/[kb_id] kb_type = "repo"

Tied to a code repository — replaces the old "ledger" concept. Stores commit-derived activity, agent memory, and discussions scoped to one repo.

sageox.ai/kb/kb_repo_3z8x...
Bubbles/SageOx Internal/sageox/scribe
SC

#sageox/scribe

Repo Member
part of @sageox-internal · 4 members

Connect

Connect your coding agent (CLI)

Auto-detects your current repo and links to this bubble.

cd ~/code/scribe && ox init

Connect Claude, Cursor, ChatGPT · MCP

One server URL per user. Access this bubble (and every other bubble you can see) via ListTeams/Search tools.

https://api.sageox.ai/api/v1/mcp

Active work

Restarting PR #231 (MoQ qmux wrap). Merged origin/main. moq_embed native tests: 13/13 green after regenerating fixtures.
#sageox/scribe 4m ago
Fixing device_register missing X-Device-ID header (firmware 0.4.x).
#sageox/scribe 20h ago

Recent commits

25e59864c feat(security): /security-review AI pipeline (#1278)
Ryan · main
2h ago
42492375e ci: bump CPU-bound jobs to ubuntu-latest-8-cores (#1276)
Ajit · main
4h ago

6. Overview — Custom KB

/kb/[kb_id] kb_type = "custom"

User-created bubble for any project, initiative, or topic. No required team or repo link; can be granted to a team or invited per-user.

sageox.ai/kb/kb_custom_q3p...
Bubbles/Q3 planning
Q3

Q3 planning

Custom Admin
#q3-planning · part of @sageox-internal · 5 members · created Apr 2, 2026
+ Have a discussion

Connect

Connect your coding agent (CLI)

Pull this bubble's context into your CLI session.

ox init

Connect Claude, Cursor, ChatGPT · MCP

One server URL per user. Access this bubble via ListTeams/Search tools.

https://api.sageox.ai/api/v1/mcp

What's in here

📚 8 notes · 3 recordings · 12 chats
Add notes, link recordings, or have a discussion to fill this bubble.

7. Overview — Channel KB

/kb/[kb_id] kb_type = "channel"

Distilled from a Slack / Mattermost channel. Membership is derived from a team grant — no direct invites. Header shows the source channel chip; feed is distilled summaries, not raw messages.

sageox.ai/kb/kb_chan_cast...
#

#cast-team

Channel Slack · #cast-team
Distilled hourly · last summary 14m ago · 312 messages this week

Latest distillations

📝
3 open questions about BRAVIA HLS audio track
18 messages · Ryan, Ajit, Galex · 1 decision recorded
14m ago
📝
Cast renderer pinned to last-good build #1218
9 messages · Ryan · production incident triage
22m ago
📝
Reactions v2 design — disagreement on whether to keep emoji counts
31 messages · 4 speakers · no decision yet
4h ago

8. Members

/kb/[kb_id]/members

Member list with role management for admins; revoke action gated on role. Personal & profile KBs don't render this tab at all.

sageox.ai/kb/kb_team_7xv2/members
SO

SageOx Internal

Team
7 members · 3 admins
+ Add member
MemberRoleJoined
RS
Ryan Snodgrass
ryan@sageox.ai
Feb 12, 2026
MR
Mihira R.
mihira@example.com
Feb 17, 2026Revoke
AB
Ajit Banerjee
ajit@sageox.ai
Feb 20, 2026Revoke
GA
Galex (AI)
galex@sageox.ai
Feb 22, 2026Revoke
DY
Dalex Yen
dalex@example.com
Mar 12, 2026Revoke

9. Settings

/kb/[kb_id]/settings admin-only

Name/slug editing + feature toggles. Non-admins get a 404. Editing slug is destructive (URL change) — confirms before save.

sageox.ai/kb/kb_team_7xv2/settings
SO

Settings

Team
SageOx Internal

Basics

Changes the URL. All inbound links use this slug.

Features

Murmurs
Surface the live agent & teammate activity feed on this bubble's overview.
Distill new content · auto-summary
Read incoming messages, recordings, and notes; pull out decisions, open questions, and key facts.
Daily summary post
Each morning, post yesterday's distilled activity to a Slack channel.
Post to: Slack · #sageox-internal Change →

Danger zone

Delete bubble
Permanently remove this bubble and all of its contents. This cannot be undone.
Delete…

10. Invite landing (public)

/kb/invite/[token] unauthenticated

Public, token-gated landing for invite links. Different states: ready, expired/revoked, flag-off, unauthenticated, loading.

sageox.ai/kb/invite/inv_8h2k...

You're invited to SageOx Internal

Invited by Ryan Snodgrass · expires May 22, 2026

A bubble is a shared thinking space — discussions, notes, and AI agent memory all in one place.

Sign in or create an account to accept.

11. Personal knowledge settings

/settings/knowledge

Where users manage their personal & profile KBs, default editor, and global KB preferences. Separate from per-bubble settings.

sageox.ai/settings/knowledge
Settings/Knowledge

Knowledge

Your personal bubbles and how agents talk to them.

Personal bubble

Yours alone · 412 notes · 14 chats
Personal
Open bubble →

Profile bubble

Optional · not yet created
Profile
Create
Editor preferences punted — needs a clearer story for whether this lives in user settings (global) or per-bubble settings. Revisit after the chat/editor surface is more defined.

§ 12. Widget gallery

Each component in isolation, every variant shown side-by-side.

12a. KB header + tabs (per kb_type)

12b. KBTypeBadge — all 6 types

12c. KBCard — list states

12d. ChannelBubbleChip

12e. EssenceCard — pre-onboarding explainer

🫧

What's a bubble?

A bubble is a private space for related thinking — notes, discussions, files, and agent memory all in one place. Personal bubbles stay with you. Team bubbles are shared. Repo bubbles tie to code.

Dismiss

12e+. "Non-existent / yet-to-be-created" pattern

proposed for sageox-design

A consistent visual vocabulary for things that don't exist yet but could: an unprovisioned profile bubble, a feature the user could enable, a section with no content yet, a member slot to fill. The pattern reads as "this is real, just empty" — distinct from disabled (greyed out, no affordance) and from loading (skeleton). Should be documented in sageox-design/DESIGN.md alongside the existing card & surface conventions.

Rules:
  1. Border: 1px dashed --ox-border (vs solid for existing things).
  2. Background: transparent or --ox-surface-2 at 50% opacity — never the full surface-1 fill that real cards use.
  3. Icon: 40% opacity. Reads as "shape of the thing" without claiming presence.
  4. Title text: --ox-text-muted, never --ox-text. Reserves bold colors for things that exist.
  5. Always pair with a CTA — non-existence is an invitation, not a reproach. Use Sage 700 for the link (matches link semantics).
  6. Anti-pattern: do not use a strikethrough, ghost-icon, or low-opacity ON A solid-border card — that reads as "disabled" / "you can't have this" and is a different signal.

12f. Bubble constellation

apps/web/src/components/kb/KBConstellation.tsx

Cluster view of every bubble you can see. Sized by activity volume, colored by kb_type, pulsing by recency. Hover any bubble for a sparkline + last-activity summary. Already in code; reused here so it's also visible in §1 view-toggle = Constellation.

Sidebar density · 280px
Ryan's personal
Personal · last active 2m ago
#sageox-internal
Team-context · 14 signals today
#sageox/sageox-app
Repo · 22 signals today
#sageox/scribe
Repo · 4 signals today
#cast-team
Channel · 6h ago
#q3-planning
Custom · 1 signal today
#sageox/ox
Repo · 2d ago
#sageox/cast
Repo · critical signal active
Right-rail widget — flat, packed, ~280px wide. Bubbles 12–28px. Hover for sparkline. Click → KB page.
Expanded variant · full route /constellation
Ryan's personal
Ryan's personal
Personal · 412 notes · 14 chats
#sageox-internal
#sageox-internal
Team-context · 14 signals today
#sageox/sageox-app
#sageox/sageox-app
Repo · 22 signals today
#sageox/scribe
#sageox/scribe
Repo · 4 signals today
#cast-team
#cast-team
Channel · 6h ago
#q3-planning
#q3-planning
Custom · 1 signal today
#sageox/ox
#sageox/ox
Repo · 2d ago
#sageox/cast
#sageox/cast
Repo · critical
Expanded variant — labels visible, more breathing room. Both variants share the same encoding (size = activity, color = kb_type, pulse = recency).
Encoding: size → 7-day activity volume; color → kb_type (flat fills, no gradients); pulse → recency. Three pulse states: fast (last hour), slow (last 24h), none (stale). Roadmap (TODOs in KBConstellation.tsx): sparkline in hover (v1.5), SSE event pulse (v1.5), cross-bubble particles for mirrored elements (v2), AI-coworker presence dots (v2), time scrubber + filter-by-coworker (v3).

12g. Onboarding stepper — two variants

Two variants serve different surfaces. Horizontal (compact) sits at the top of the KB overview — lightweight, hover-revealed help on each step. Vertical (descriptive) matches the existing GettingStartedWidget.tsx pattern — heavier, denser, surfaces "what this step actually means" without hover. Both variants share the same completion state, so progress in one reflects in the other. Stored as kb.metadata.viewer_onboarding_completed_at_<user_id>.

Variant A — Horizontal (compact). Hover any step for help; current step shows its tip pinned.
Bubble provisioned
2Connect your tools
3Have your first discussion
4Invite teammates
The current step shows its tooltip pinned-open (.show-tip) so the user gets context without having to hover. Other steps reveal on hover. aria-describedby wiring in the React version makes it screen-reader-accessible — the CSS-only tooltip here is a visual stand-in.
Variant B — Vertical (descriptive). Each step explains itself inline.
🚀
Get this bubble going
1 of 4 done · tracked for @ryan
Bubble provisioned
Done automatically when the bubble was created.
Done
2
Connect your tools
Wire up at least one of CLI (ox init), MCP, or a Slack channel so your agents and teammates can read & write into this bubble.
Continue →
3
Have your first discussion
Record a meeting or dictate a memo. The transcript and decisions land in this bubble and become searchable.
4
Invite teammates
Send a magic link or pick from your team. Invited teammates get Member role by default.
When to use which: Horizontal lives at the top of KB overviews — high-frequency surface, low pixel budget. Vertical lives in a sidebar widget or dedicated onboarding pane — used on first-touch routes (/welcome, /home, freshly-created bubbles) where the user needs more guidance. The vertical variant mirrors the existing GettingStartedWidget.tsx on team pages.

12h. Connect widgets (proposed)

Three cards. The Slack card is conditional on kb_type — personal/profile bubbles omit it; repo bubbles hide it by default.

Connect your coding agent (CLI)

Pull this bubble's context into your CLI — Claude Code, Cursor, or any ox-aware agent.

ox init

Connect Claude, Cursor, ChatGPT · MCP

One server URL per user. JWT-authenticated. Works with Claude Desktop, Cursor, ChatGPT, and any MCP client.

https://api.sageox.ai/api/v1/mcp
#

Connect a Slack channel

Pipe a channel into this bubble. Distilled discussions appear in the feed; raw messages aren't stored.

+ Link a channel

12i. Murmurs — BEFORE vs AFTER

Today's row vs the redesign. AFTER adds: principal avatar + name (the human the agent acted on behalf of), "via {agent}" sliver, KB provenance chip, timestamp tooltip, freshness states (live / recent / stale), importance variants (critical / ambient), human-posted variant (no via fragment).

Before — today

Restarting PR #231 (MoQ qmux wrap). Merged origin/main into ajit/moq-qmux-wrap (clean). moq_embed native tests: conformance 13/13 green after regenerating fixtures; 2 stale unit tests lag the impl (ws_frame subprotocol offer, publisher JWT-in-URL).
4h ago
Fixing device_register missing X-Device-ID header (firmware 0.4.x). Patched device_api.cpp + esp_http_client_impl.cpp; testing on hardware via release flash.
20h ago
Missing: who, which bubble, exact time, freshness distinction (4h-old vs 20h-old look identical).

After — redesigned

Restarting PR #231 (MoQ qmux wrap). Merged origin/main into ajit/moq-qmux-wrap (clean). moq_embed native tests: 13/13 green.
#sageox/scribe 4h agoThu, May 15, 2026 · 10:26 PT
2026-05-15T17:26:11Z
moq_embed/native_test.cc +3
Fixing device_register missing X-Device-ID header (firmware 0.4.x). Patched device_api.cpp + esp_http_client_impl.cpp.
#sageox/scribe 20h agoWed, May 14, 2026 · 18:42 PT
2026-05-15T01:42:13Z
hover, pinned
Sketching Q3 KB roadmap — three workstreams: (1) connect widgets, (2) onboarding stepper, (3) attribution rework on murmurs. Linking ADR-030 and Galex's discussion.
👥 Team 8m agoThu, May 15, 2026 · 14:22 PT
2026-05-15T21:22:48Z
Production cast-renderer dropping audio on BRAVIA — pinning to last-good #1218.
#sageox/cast 6m agoThu, May 15, 2026 · 14:24 PT
2026-05-15T21:24:01Z
Renaming "shadow repo" → "ledger" across docs and CLI strings. Please refer to the new term going forward.
👥 Team 12m agoThu, May 15, 2026 · 14:18 PT
2026-05-15T21:18:33Z
Background heartbeat: re-indexing Q3 planning bubble after merge.
📖 Q3 planning 2h ago
Avatar + name
principal_id resolved to the human user the agent acted on behalf of
"via" + agent glyph
agent_type ({claude-code, github-copilot, …}). Absent when a human posted directly.
KB chip
kb_type + name. ⎇ org/name for repo, 👥 Team, 📖 Custom, # for Channel.
Dot state
← (topic + age). Pulse: wip < 4h. Solid: wip 4–24h. Hollow: > 24h.
Timestamp tooltip
timestamp. Both localized + ISO for log-grep paste.
Border + text color
importance. Copper border for critical; muted/0.7 for ambient.
for the engineer rebuilding MurmurItem.tsx

No API changes required. Every field in the new design is already on the wire: principal_id, agent_type, topic, importance, repo_id, timestamp, metadata.files. This is a render-layer change only.

Row grid. grid-template-columns: 16px minmax(0,1fr) auto. Dot · content · meta. Don't let the meta wrap — it's the right-edge anchor for scanning. Time + KB chip stack in the meta column with flex-direction: column; align-items: flex-end.

Principal-first attribution. The byline shows the human the agent acted on behalf of, not the agent. Resolve principal_id → user → avatar + first name. agent_type follows as · via ✦ claude-code. When agent_type is empty (human posted), drop the entire "via …" fragment — don't replace it with "manual" or a person icon. Absence is the signal.

KB provenance chip. Distinguished by icon, not color. ⎇ org/repo for repo, 👥 Team for team-context, 📖 Name for custom, #name for channel. 11px font, mono only for repo paths. Tooltip on hover gives full KB type + activity count.

Freshness states. wip + <4h = pulsing green dot. wip + 4-24h = solid green. Anything >24h = hollow ring (border: 1.5px solid var(--char-500); bg: transparent). The content text drops to text-muted on stale rows. The biggest legibility win — without this, a 4m-old murmur and a 20h-old murmur look identical.

Importance variants. critical → 2px copper-400 left border + content stays default color (don't double-signal). ambient → row opacity 0.7. normal → no extra treatment.

File chips. Max 2 visible, then +N more. Mono, 10px, surface-2 bg, text-muted. Hover expands. Calm mode hides them entirely until row hover.

Timestamp tooltip. Two-line: localized + ISO. <time dateTime={iso}> for a11y and copy-paste. Use Radix Tooltip with delayDuration={300}, fade-only entry. No slide, no scale.

Fixed-height + decay (V2.2). Wrap the feed in .murmur-feed.fixed.decay when it lives on a KB or team overview. Row 1 = 1.0 opacity, row 2 = 0.95, row 3+ = 0.88. .see-more footer with gradient fade-in. Hover any decayed row restores 1.0.

Files. Rewrite apps/web/src/components/murmurs/MurmurItem.tsx. Container: MurmurFeed.tsx takes new props fixed?: boolean, decay?: boolean, seeMoreLink?: string.

12j. Chat interface — "ask the bubble"

ADR-039 Coworker Chat

A primary surface for interrogating a KB. Lives as a right-rail dock on KB overview pages and as a full-page route /kb/[kb_id]/chat/[thread_id]. The assistant answers grounded in the bubble's content; citations link back to the source notes, discussions, or messages. BYOK from Phase 1 (per ADR-039) — each user's grants drive the model used.

Empty state · first open
Ask #sageox-internal
Ask the bubble
Grounded in this bubble's notes, discussions, and files. Citations link back to the source.
Recording off Turn on to publish this chat to #sageox-internal
Scope #sageox-internal + widen
send · ⇧↵ newline · / commands · grounded in this bubble
Active conversation · hover toolbar + citation card
Can teams own bubbles directly?
RS
What did we decide about KB ownership — can teams own bubbles directly?
Per the May 12 architecture discussion[1]DiscussionKB architecture — ownership model"…we'd want to keep the human creator on the record for audit, but allow team-owned bubbles. Galex proposed a polymorphic owner_type column…"Open discussion → and ADR-030[2], teams can have many bubbles. Today's schema uses owner_user_id (single human) plus aliased_team_id[3] for the team-context KB. The team-ownership generalization is noted but not yet drafted as an ADR.
RS
Show me where aliased_team_id is referenced.
Recording on · publishes to #sageox-internal when ended 5 turns · 2m
Scope #sageox-internal @sageox-internal + widen
send · ⇧↵ newline · / commands
Collapsed affordance · floats bottom-right on every KB page
Ask #sageox-internal ⌘ J
Model + BYOK picker · click the ⚙ gear icon in the chat head
Chat settings →
Current
claude-sonnet-4-6 your key · 1.2k tokens today
Reasoning
claude-sonnet-4-6BYOK
claude-opus-4-7BYOK
gpt-5team
Fast
claude-haiku-4-5BYOK
Manage
Add a model key…
Use team's keys (granted)
Slash commands · type / at the start of a message
/scopeNarrow or widen which bubbles this thread can retrieve from
/citeFind sources for the previous answer
/saveSave the last assistant message as a note
/recordToggle session recording for this thread
/clearStart over without losing this thread
Thread history · sidebar on /kb/[id]/chat
Can teams own bubbles directly?
"Per the May 12 discussion and ADR-030, teams can have many bubbles, but the schema uses owner_user_id…"
5 turns · claude-sonnet-4-6 · recording
now
BRAVIA HLS audio drop — root cause?
"The fMP4 segments are missing the audio track on Sony receivers when the source is video-only…"
12 turns · claude-sonnet-4-6 · saved as discussion
2h
What's in #q3-planning?
"Three workstreams currently active: customer research, pricing experiments, and the KB rollout plan…"
3 turns · gpt-5
3d
Design intent.
  • Quiet head. No model/BYOK/scope info as visible text — that chrome competes with the thread title. A single ⚙ gear icon reveals the model picker (which includes BYOK status, token usage, team-key fallback) on click. Hover tooltip gives the current model at-a-glance.
  • Two rails, two voices. The user's "ask" gets a 2px copper right-rail; the assistant's "answer" gets a 2px sage left-rail. No chat bubbles — just typography and rules.
  • Citations earn their pixels. Inline superscripts [ex], hover for a card with source title + snippet + Open link. Wikipedia-grade, not chip-row.
  • Composer focus is calm. Border lightens to sage on focus. Send button sits ghost until you type. No spring, no slide.
  • Recording is a deliberate choice. Off by default. When on, a quiet red strip sits between body and composer. Recorded threads publish to the bubble when ended.
  • Scope is a chip. Default to the current bubble. Widen via chip — easier than a settings dialog, more discoverable than a slash command.

12k. Calm-by-default + power-on-demand DS

proposed for sageox-design

Two design principles SageOx should codify in sageox-design/DESIGN.md: (1) progressive disclosure — non-technical users see plain language by default, with the technical surface reachable but never in the way; (2) calm-by-default — visual-spatial / hypersensitive users see a UI that is crisp, predictable, and low-motion. Both audiences get the same product; only the surface area is staged. The mockup header now exposes a Calm mode toggle (top-right) — try it to see the V2 with all looping animations suppressed.

Design-system additions proposed for sageox-design/DESIGN.md:
  1. Plain-language hierarchy: human name leads, technical handle follows in --text-muted one size smaller. Applies to KB headers, settings labels, connect-card titles, anywhere a slug/protocol/ID would otherwise lead.
  2. Three-layer motion policy: (a) honor prefers-reduced-motion globally, (b) ship a user-level "Calm mode" preference, (c) cap to 3 looping animations on screen at once.
  3. Color + shape redundancy: any color-coded state must pair with shape, position, or letterform. Color alone is never the signal.
  4. Power-on-demand: every keyboard shortcut has a visible button. Every slash command has a chip equivalent. Every ID has a slug. Power users get density; new users get affordances.
  5. Contrast lock: all text tokens MUST clear 4.5:1 on their host surface. Add a Storybook check that fails the PR on contrast regression.
  6. Empty-state vocabulary: "doesn't exist yet" pattern (dashed border + muted + Sage CTA, see §12e+) is the canonical "this is real, just empty" treatment — distinct from disabled and from loading.
for the engineer implementing calm mode + a11y

Four-layer motion + density policy.

  1. OS reduced-motion (nuclear). @media (prefers-reduced-motion: reduce) at the top of the global stylesheet. Sets animation-duration: 0.001ms, animation-iteration-count: 1, transition-duration: 0.001ms on *, *::before, *::after with !important. No opt-in.
  2. Calm mode (user preference). Body attribute data-calm="true". Persists in user settings; default false. Targets only LOOPING animations (.act-pulse.live::after, .bubble-cluster .b.pulse::before, .rec-strip .rec-dot, .chat-typing span, .ambient .a-dot.live::after) — sets animation: none. Keeps hover/focus transitions but slows them to 120ms. ALSO triggers density reduction.
  3. Density reduction (calm mode). Selectors that hide ancillary metadata: .murmur-files hidden, byline "via X" hidden, KB chip opacity 0.35, sidebar ambient labels hidden, card descriptions opacity 0.55. Hover the host row to reveal. Bottom-left pill reminds the user "Calm mode · hover any row for full detail".
  4. Animation budget. Enforced by reviewer / lint: no more than 3 looping animations visible at once. A page that has the murmur dot pulse + recording strip + ambient strip live indicator + bubble cluster pulse is already over budget.

Contrast lock. Every text token MUST clear 4.5:1 against its host surface in both modes. V2.1 raised dark-mode --text-muted (was #8f99a3 → now #9aa3ab) and --text-subtle (was #6f767c → now #7d858c) after a contrast audit. Add a Storybook check that fails the PR on contrast regression.

Plain-language hierarchy. Human-readable name leads. Technical handle (slug, ID, protocol name) sits in --text-muted at one size smaller. Applied via the pattern: <span>Distill new content <span class="muted-suffix">· auto-summary</span></span>. Codify as a typography mixin in sageox-design.

Power-on-demand. Every keyboard shortcut MUST have a visible button. Every slash command MUST have a chip equivalent. Every ID MUST have a slug equivalent. Document in DESIGN.md.

Files. Global stylesheet (apps/web/src/index.css). New user setting under /settings/preferences for the Calm mode toggle. Hook: useCalmMode() reads from local state + persists to user prefs API.

12l. State variants

Mockup file: .context/kb-pages-mockup.html · Tokens from packages/ui/styles/tokens.css · Motion per .claude/rules/motion-sageox.md.