Skip to content

feat(ui): add Data Rooms template - #47

Merged
yyyyaaa merged 1 commit into
mainfrom
phat/data-rooms
Oct 1, 2026
Merged

yyyyaaa merged 1 commit into
mainfrom
phat/data-rooms

Conversation

@yyyyaaa

@yyyyaaa yyyyaaa commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Adds Data Rooms, a new application template next to Agents Builder and Billing: secure document rooms for an organization and its sub-organizations.

  • Rooms by unit: organization → sub-organizations (e.g. funds, teams) → rooms, with a room-centric workspace (Documents · People · Q&A · Insights · Settings).
  • Access you can explain: owners and admins carry down the hierarchy, members only into open rooms, folder shares cover a subtree until they expire, read-only and view-only remove writes and downloads. A "Why can they?" sheet, an access matrix, and an Access map canvas show where every permission comes from.
  • Preview as: room managers see the workspace exactly as someone else would, with changes locked.
  • Deal-room features: agreement gate before entry, view-only and watermarked viewer, Q&A where guests only see their own company's threads, reading insights, and a room assistant that only cites documents the reader can open.
  • Built on the shared kits: workspace-kit shell, panels, tables and canvas; the AI kit for the assistant; kit Select, Checkbox and Textarea.
flowchart LR
  Org[Organization] -->|owners, admins| Unit[Sub-organization]
  Unit -->|owners, admins| Room
  Unit -.->|members, open rooms only| Room
  Room -->|role + extra grants| Docs[Folders & documents]
  Share[Folder share, until a date] --> Docs
Loading

Contract: one data object, every change through onCommand (reject to keep the dialog open), host-owned controls through onAction, optional renderPreview and onAskAssistant.

<DataRooms
  data={data}
  onCommand={async (command) => {
    await save(command);
    router.refresh();
  }}
  onAction={(action) => action.type === 'download' && download(action.documentId)}
/>

The access helpers (effectiveAccess, visibleDocuments, visibleQuestions, visibleActivity, describeGrant) and applyDataRoomsCommand are exported, pure, and cached per data snapshot; they drive the UI and can run on the server, which still decides every request.

Wiring: registry item data-rooms with smoke-install case, Storybook (Templates/DataRooms), flow, SSR and access tests, docs page at /blocks/data-rooms with live preview, and agent skill routing.

Source: packages/ui/src/components/data-rooms

🤖 Generated with Claude Code

https://claude.ai/code/session_01GPTLDuvMBGBawVLKL42yYV

- Secure document rooms owned by an organization and its sub-organizations,
  with access that carries down the hierarchy, expiring folder shares,
  agreements before entry, view-only and watermarked documents, Q&A,
  reading insights, an access map, and a room assistant that only cites
  documents the reader can open
- Pure, cached access helpers (effectiveAccess, visibleDocuments,
  visibleQuestions, visibleActivity, describeGrant) drive UI gating, the
  "Why can they?" trace, and Preview as
- data + onCommand + onAction contract with optimistic local updates via
  applyDataRoomsCommand
- Registry item, Storybook stories, flow/SSR/access tests, docs page and
  preview, and agent skill routing
@tenki-reviewer

tenki-reviewer Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review complete. 🟡 2 medium

💬 Inline comments (2)


The change introduces a complete data-rooms block under packages/ui/src/components/data-rooms — an access model with inheritance, expiry, and agreement gating; room, documents, people, Q&A, insights, and settings views; demo fixtures and a scripted assistant — and wires it into the registry manifest, smoke-install checks, the docs app (hub page, preview route, showcase canvas, Tailwind @source), skill docs, and flow-level tests.

Files Change
packages/ui/src/components/data-rooms/* (~40 files) New block source: access computations, context/command pipeline, dialogs, tabs, views, and demo data.
packages/ui/registry.json, apps/registry/scripts/smoke-install.ts Registry entry for data-rooms and smoke-install coverage of its dependency list.
apps/blocks/src/** (pages, showcase, navigation, globals.css, llms.txt test) Docs-app hub page, preview route, canvas wiring, and Tailwind source registration for the new block.
.agents/skills/constructive-blocks/**, scripts/constructive-blocks-skill.test.ts Skill reference updates and verification guidance for the data-rooms surface.
packages/ui/test/*, packages/ui/stories/DataRooms.stories.tsx Access, SSR, and behavior tests plus a Storybook story for the block.

Reviewed commit: db8350b

@yyyyaaa
yyyyaaa merged commit 5ecd87f into main Oct 1, 2026
5 checks passed

@tenki-reviewer tenki-reviewer 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.

This PR adds a self-installable data-rooms application block (rooms, access model, Q&A, insights, settings) to the @constructive registry plus docs-app integration, and two medium-severity logic bugs were found in the demo command/insight helpers.

Key findings

Comment on lines +42 to +47
function niceMax(value: number) {
if (value <= 5) return 5;
const magnitude = 10 ** Math.floor(Math.log10(value));
const step = [1, 2, 2.5, 5, 10].map((factor) => factor * magnitude).find((candidate) => candidate * 4 >= value) ?? magnitude * 10;
return step * 4;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 bug · medium

Fix niceMax chart axis scale overshoot

niceMax returns an axis top far above its documented values: it computes magnitude from the raw value (10 ** Math.floor(Math.log10(value)), insights-tab.tsx:44) and then returns step * 4, where step is the smallest nice number whose quadruple already covers the value. For the comment's own examples the function returns niceMax(13) = 40 (promised 15), niceMax(42) = 80 (promised 50), and even niceMax(6) = 40 (promised 10), because for any value above 5 the magnitude is at least 10. The y-axis of the "Views and downloads per day" chart ends up 3-8x taller than the tallest bar, squashing every bar toward the baseline whenever the 14-day peak is between 6 and 39 events.

📋 Prompt for AI Agents

In packages/ui/src/components/data-rooms/insights-tab.tsx at lines 42-47, fix niceMax so it returns the documented axis tops (4 → 5, 13 → 15, 42 → 50). The bug is that magnitude is computed from the raw value, so step * 4 overshoots by 3-8x. Change the magnitude to be based on value/4, e.g. const magnitude = 10 ** Math.floor(Math.log10(value / 4)), keeping the existing candidate list and the step * 4 return; verify niceMax(4)=5, niceMax(6)=10, niceMax(13)=15, niceMax(42)=50.

Comment on lines +55 to +56
const existing = memberships.find((membership) => membership.personId === known.id && membership.scope.kind === 'room' && membership.scope.id === command.roomId);
if (existing) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 bug · medium

Re-inviting a suspended or expired member silently no-ops

The invite handler in applyDataRoomsCommand skips a known person whenever any room membership already exists, without checking status or expiresAt (commands.ts:55-56). A suspended or expired member is therefore never re-added, no RoomInvite row is created, and no activity event is logged — the optimistic apply, which hosts are told mirrors server behavior, silently does nothing. Re-inviting an expired guest appears to succeed in the UI but grants no access.

📋 Prompt for AI Agents

In packages/ui/src/components/data-rooms/commands.ts around lines 53-66, change the invite case so the existing membership check only skips people with an active, unexpired membership. When the existing membership is 'suspended', 'invited', or past its expiresAt, update it back to active (refreshing roleId/expiresAt) rather than continue-ing, so a re-invite grants access and logs an invite activity event.

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