Skip to content

Commit 3fe70ac

Browse files
committed
Consolidate Platform controls and align Home and Chat
1 parent f405705 commit 3fe70ac

84 files changed

Lines changed: 1221 additions & 1083 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.agents/skills/implement-platform/SKILL.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Read `apps/sim/platform/AGENTS.md` first. It is the canonical ownership and code
1010
1. **Scope the phase.** Foundations, recipes and evidence are Phase 1. Approved product screens and their unique parts are Phase 2. Route integration, persisted state and authorization belong to Phase 3. Keep UI-only requests isolated; do not advance phases just to make a demo look real.
1111
2. **Resolve the product change.** Inspect the actual route first. Use the relevant live Paper bases and product states for existing anatomy, theme roles and interactions; a later explicit code-first decision supersedes an older specimen. Keep the checkpoint scoped to the affected states. For a new idea, a real product use is required, a new Paper drawing is not. Exclude Marcus.
1212
3. **Choose one owner.** Use `app/(shell)/<route>/_components/<component>/<component>.tsx` and a sibling CSS Module for unique anatomy. Put shared shell compositions at the nearest common route owner, generic EMCN primitives in `design-system/components/<family>/`, and design roles in foundations. Keep pages style-free. Promote the original owner on second use and update both callers. A gallery alone never justifies a variant. Apply the shared rhythm, icon and hover contract in `apps/sim/platform/AGENTS.md`; compare two real consumers for title and Cancel placement, first-line leading art and centered trailing metadata, opaque type-scaled icons, row hover and compact action gaps. Keep form chrome in Modal. Verify long-content fades on the actual scroll owner at the start, middle and end, then filter or resize until all content fits.
13+
Consult the tracked component ownership map before adding markup. Choose the existing action button, navigable row, two-line activity row, picker, or disclosure by behavior. Render Base UI triggers through their controls. Trace copy/status lifetimes to the shared expiry owner; verify pause, reset, reduced motion and long tooltip limits. Compare Home/Chat reading edges and stable controls across responsive layouts, including structural rules and the actual scroll boundary.
1314
4. **Translate anatomy, not artboard coordinates.** Consult the installed Next docs in `node_modules/next/dist/docs` (especially project structure, CSS, Server/Client Components). Use semantic DOM, native or accessible behaviors, intrinsic size and responsive relationships. A 560px Paper modal is a preferred maximum, not a fixed width. The same modal should fit 647px and 390px viewports without new variants. Use centralized Tailwind roles, the type and material contracts, shared icon geometry and slots. Confirm the rendered DOM has the intended behavior. A searchable field accepts text, a disclosure opens by keyboard, a modal part composes directly, and a table renders the caller’s records. Repeated data takes an array, never a fixed list of numbered specimen props. Register a named native component for these cases. A new size requires a distinct, evidenced use; it isn't a device enum. Never pass styles through a composition. Trace repeated rows to their registered item and preserve the surrounding layout. Accept a typed collection, then render empty, short and longer-than-specimen data to prove that any example count can change. Keep different semantic roles distinct. Wrap Base UI primitives in EMCN when behavior involves floating layers, list navigation, cascading actions or focus management. Main-view and modal dropdowns share the same surface and rows. Preserve the caller’s value, accessible label and form submission through the owner. When editing generated files, change the source contract or compiler and regenerate the affected set.
1415
5. **Exercise the right states.** Build a matrix of actual themes, relevant content sizes, resting, hover, selected, error and disabled states, keyboard navigation, long data, zoom and widths between Paper examples. The workbench is tooling, not a Phase 2 product screen. Entities, including organizations, workspaces, members and chats, come only through the server fixture gate. Render empty or absent data distinctly. Focusing a control or typing in it preserves its resting chrome; verify there is no focus paint on the control or its wrappers. Test keyboard focus and restoration separately. For cascading menus, test hover travel, arrow keys, Escape one level at a time, and bounds at either edge of the viewport and inside a modal. Client interaction may open, dismiss or change steps; no authentication, persistence or network effects.
1516
6. **Prove it.** Run the structural audit against real code, regenerate/check relevant files, render the app in a real browser, and compare rendered geometry, type, icon ink/weight and interactions with the product contract and relevant Paper states. Exercise empty, full, stress and flag-off. Retire unused variants and resolve every affected use. Verify isolation and the repository gates. Report covered keys/states separately from visual comparisons; record unresolved differences and durable verification paths. Stop at the authorized phase.

‎apps/sim/platform/AGENTS.md‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,19 @@ Keep routes thin. Colocate each feature component, its CSS Module and private he
2222

2323
Use `design-system/styles/rhythm.css` for title/detail, list and action gaps (2px), row insets and icon sizing. Separate sibling rows with the sidebar list gap, without divider rules. Borders delineating surfaces and actual data rules, such as chart axes, serve different purposes. Align a leading avatar or glyph with the first text line; center trailing metadata, badges and actions against the whole row. Align action ink with adjacent text by extending the hit area into the margin. Chat thinking, tool labels and prose share that text edge. Composer and message action groups use the same compact gap. Custom calendar, chart and summary rows use the same type, spacing and tooltips.
2424

25-
Home, organization and profile share the sidebar button recipe. Row-size icon controls use its hit area; compact controls change target size while retaining the same glyph scale. Show disclosure chevrons while hovering anywhere on their row. Only composer action buttons are circular; binary switches and segmented switch tracks keep their own capsule geometry.
25+
Home and Chat share a centered, border-box reading column (maximum width and inline gutter). Put the 24px resting Home inset **inside** the scroll content. The mask belongs to the actual viewport and stays at the window edge. Use the same 24px step between major dashboard/sidebar sections and on both sides of the sidebar footer. Responsive layouts can wrap, collapse or split panels; button height, padding and type stay stable.
2626

27-
Measure the actual scroll viewport with `ScrollArea`, or attach `useScrollEdges` directly to a native or Base UI scroll owner. Use the shared 16px mask only on edges with more content, updating after scroll, resize, input or filtering. Do not substitute permanent top/bottom padding or a fixed gradient for overflow. Keep legitimate insets for form surfaces and native window controls. Exercise empty, overflowing and filtered states, including horizontal truncation.
27+
Use `ControlRow` for the sidebar, identity controls, panel tabs, recent links, wrapping workspace choices and horizontal destination links. The row owns its 30px height, 8px inset and the placement and hover of a 24px action, including a leading remove button that replaces the glyph. Keep the link, tab or button semantics on the rendered element, with sibling actions outside it. Whole section headers reveal their chevron on hover; identity controls need none. Ordinary text and icon triggers use `Button` / `IconButton`, including Base UI menu triggers. Pass icons and labels through their slots. Binary switches stay pill-shaped; clicking the selected half of a two-choice segment switches to the other half. Native keyboard choice navigation still selects its destination.
2828

29-
Form dialogs use `Modal` or `MultiStepModal`. Pass a title, content, action labels and handlers. The shared owner positions the title at the logical start and supplies a single footer with a quiet Cancel/Back. Product code cannot construct or style a form header, footer or Cancel button. Named search and media dialogs have their own content anatomy inside the shared accessible overlay.
29+
Use `ActivityRow` for a first-line glyph or avatar, title/detail and centered trailing metadata. Use the chart, calendar, composer or attachment owner when those parts have a different interaction. See the [component ownership map](docs/component-ownership.md) before adding another family.
30+
31+
Measure the actual scroll viewport with `ScrollArea`, or attach `useScrollEdges` directly to a native or Base UI scroll owner. Use the shared 16px mask only on edges with more content, updating after scroll, resize, input, reset or filtering. Insets establish resting layout; they cannot substitute for an edge fade. Scroll the calendar through the entire day and the message list through its full inbox independently of Home. Plain tooltips preview at most three lines; retain the complete accessible text. A structured people popover owns its own layout.
32+
33+
Form dialogs use `Modal` or `MultiStepModal`. Pass a title, content, action labels and handlers. The shared borderless surface positions its title at the logical start and supplies one quiet Cancel/Back. Command search and previews use the same shadow and dismissal layer. Form controls cannot construct or style a form header, footer or Cancel button.
34+
35+
Use `useClipboard` / `CopyButton` for successful copy feedback and `useFeedbackExpiry` for temporary status. Copied indicators and transient notices dwell for four seconds; repeated events restart the timer. Pause an actionable notice during hover or focus and keep its action available in the stack afterward. Icon color changes use `--ui-motion-color` (120ms, zero under reduced motion). Do not put literal timers or CSS durations in product components.
36+
37+
Chat tool summaries use one body-size label with quieter ink. A run divider, expanded tool rail and split-panel edge are structural rules with one shared hairline role. Ordinary list items remain separated by gap alone. Panel tabs, action controls and key values have their own semantics; the same CopyButton copies message text and either tool payload. Composer and jump-to-latest share their border, background and shadow; leave space between them.
3038

3139
Interface strokes have one non-scaling optical weight in CSS pixels. Size glyphs relative to the control’s type, capped at the shared icon size. Paint interface strokes with opaque semantic ink so overlapping segments do not darken; branded artwork and chart marks retain their own geometry. Quiet controls gain the hover surface without changing ink. A selectable menu row gains the same surface and promotes its glyph from secondary to primary ink on hover or active keyboard navigation. Selection, disabled and inverse controls have distinct semantics. Never change stroke thickness, spacing or radius on hover. Render interface glyphs through `Icon`. A unique data graphic may own accessible, labeled SVG and paint its own marks. DOM focus alone adds no paint.
3240

@@ -48,7 +56,7 @@ Interface strokes have one non-scaling optical weight in CSS pixels. Size glyphs
4856
- Keep a draft or interface edit in memory for the lifetime its user expects. A chat draft belongs to its chat; profile edits and inbox read marks belong to the product session. Navigating away and back must preserve them. A new fixture scenario starts a fresh session. Popups and cancelled edits stay local to the control.
4957
- The same product entry under `app/(shell)/` serves the web browser and the existing `apps/desktop` Electron shell. Feature-detect `window.simDesktop` in the typed desktop adapter only. Consult the shared `@sim/desktop-bridge` contract and native titlebar observer. Electron owns real window controls and privileged operations. Reserve their inset only in a native window; keep interactive areas outside drag regions. Do not draw traffic lights, mock the bridge in product code, or fork pages by viewport or user agent.
5058
- On every shell or panel change, test the real Electron window as well as web, keyboard, narrow and intermediate layouts. Exercise inset/fullscreen, sidebar expand/collapse, panel takeover, tabs and focus return. Browser-only views must remain usable without the preload. Native capability integration remains Phase 3.
51-
- Keep unique content in its feature owner. A calendar event, attention row or two-line workspace summary can have its own anatomy. Reuse semantic ink, type, icon size, first-line alignment, row insets and spacing. Its overflow trigger still uses EMCN's icon button/menu; do not extract a generic row on resemblance alone. When a second feature needs the same semantic recipe, promote the original implementation and migrate both consumers.
59+
- Keep data-specific layouts such as calendar time slots, chart marks and tool payloads in their feature owner. Promote a repeated interactive row to EMCN and migrate the original consumer; attachment previews, form controls, menus and data actions each retain their semantic owner.
5260

5361
## Verification
5462

‎apps/sim/platform/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ Shared and Platform still own different recipes; `catalog/evidence.json` preserv
3939

4040
Workspace lists, sidebar items, recents and attachment batches accept typed arrays with stable item IDs. Use the named components for these collection masters: a Paper specimen's item count is sample data. An empty array renders zero items, and adding items preserves their registered row/card appearance. Each scroll viewport determines fades and controls from its actual contents. Chart labels, marks, inspected values and legends come from the same series. Supply raw numeric values and their units to data tables. The column schema determines alignment and which columns remain visible as the table narrows.
4141

42-
The full scenario includes long email descriptions, twenty workspaces and sixteen linked conversations. Open Home at normal or narrow width to see clipping, scrolling and the list rhythm. Sidebar groups, command results and other scrollable owners share a directional fade that clears at the boundary. The same rules apply in Electron.
42+
The full fixture includes twenty sidebar workspaces, sixteen conversations, a twelve-message inbox and a calendar that scrolls from midnight to midnight even on empty days. Home and Chat share a reading width. Home content starts at the same 24px inset used between major sidebar sections. Content scrolls through the viewport edge fade.
4343

44-
Form dialogs receive their title and actions as data. EMCN owns title alignment and the quiet Cancel button, including multi-step forms. The [design contract](AGENTS.md) defines the shared button sizes, switch shapes, row alignment, icon ink and gaps.
44+
Start with the [component ownership map](docs/component-ownership.md) when choosing a primitive. Sidebar, recents and panel tabs use one row; form and command dialogs use one borderless surface. Copy confirmations and transient notifications last four seconds. Notifications pause under the pointer or keyboard focus; icon color transitions take 120ms and respect reduced motion. The [design contract](AGENTS.md) defines these rules.
4545

4646
### Keyboard and dropdown behavior
4747

‎apps/sim/platform/app/(shell)/_components/composer/composer.module.css‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@
44
gap: var(--spacing-16);
55
min-inline-size: 0;
66
padding: var(--spacing-16) var(--spacing-12) var(--spacing-12);
7-
border: var(--spacing-stroke-default) solid var(--ui-divider);
87
border-radius: var(--radius-composer);
9-
box-shadow:
10-
inset 0 var(--spacing-1) 0 var(--ui-highlight),
11-
0 var(--spacing-2) var(--spacing-6) var(--ui-shadow-control);
128
}
139
.composer textarea {
1410
max-block-size: min(24dvh, var(--container-208));

‎apps/sim/platform/app/(shell)/_components/composer/message-attachments.tsx‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import { useEffect, useState } from 'react'
44
import styles from '@/platform/app/(shell)/_components/composer/composer.module.css'
55
import type { ChatAttachment } from '@/platform/app/(shell)/_components/composer/model'
6-
import { usePlatformTheme } from '@/platform/app/(shell)/_components/session/product-surface'
76
import { AttachmentGrid } from '@/platform/design-system/components/attachments/attachment-grid'
87
import { useFileUrl } from '@/platform/design-system/components/attachments/use-file-url'
98
import { OverlayDialog } from '@/platform/design-system/components/modal/overlay-dialog'
@@ -17,7 +16,6 @@ interface MessageAttachmentsProps {
1716
export function MessageAttachments({ items, onRemove }: MessageAttachmentsProps) {
1817
const [expanded, setExpanded] = useState(false)
1918
const [preview, setPreview] = useState<ChatAttachment | null>(null)
20-
const theme = usePlatformTheme()
2119
if (!items.length) return null
2220
const batch = items.length > 3
2321
return (
@@ -45,7 +43,6 @@ export function MessageAttachments({ items, onRemove }: MessageAttachmentsProps)
4543
previewFile: item.file,
4644
}))}
4745
layout={expanded ? 'list' : 'grid'}
48-
theme={theme}
4946
onPreview={(id) => setPreview(items.find((item) => item.id === id) ?? null)}
5047
onRemove={onRemove}
5148
/>

‎apps/sim/platform/app/(shell)/_components/composer/message-composer.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export function MessageComposer({
121121
<div
122122
className={styles.composer}
123123
data-material='reading'
124+
data-elevation='composer'
124125
onKeyDownCapture={(event) => {
125126
if (
126127
slash !== 'typed' ||

‎apps/sim/platform/app/(shell)/_components/composer/model.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export interface ChatTurn {
2727
steps: {
2828
id: string
2929
name: string
30-
detail?: string
3130
time: string
3231
input: string
3332
output: string

‎apps/sim/platform/app/(shell)/_components/inbox/inbox.module.css‎

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@
55
);
66
padding: var(--ui-popup-padding);
77
}
8-
.inboxHeader,
9-
.inboxRow {
8+
.inboxHeader {
109
display: flex;
1110
align-items: center;
1211
padding: var(--ui-row-block) var(--ui-row-inset);
1312
gap: var(--ui-row-gap);
14-
}
15-
.inboxHeader {
1613
justify-content: space-between;
1714
}
1815
.list {
@@ -22,29 +19,7 @@
2219
padding: 0;
2320
margin: 0;
2421
}
25-
.inboxRow {
26-
inline-size: 100%;
27-
text-align: start;
28-
border-radius: var(--ui-control-radius);
29-
cursor: pointer;
30-
}
31-
.inboxRow:hover {
32-
background: var(--ui-hover);
33-
}
34-
.inboxRow > svg:first-child {
35-
align-self: flex-start;
36-
}
37-
.copy {
38-
display: grid;
39-
min-inline-size: 0;
40-
flex: 1;
41-
gap: var(--ui-stack-gap);
42-
overflow-wrap: anywhere;
43-
}
44-
.copy > span + span,
4522
.empty {
4623
color: var(--ui-secondary);
47-
}
48-
.empty {
4924
padding: var(--spacing-12) var(--ui-row-inset);
5025
}

‎apps/sim/platform/app/(shell)/_components/inbox/inbox.tsx‎

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { type RefObject, useState } from 'react'
44
import styles from '@/platform/app/(shell)/_components/inbox/inbox.module.css'
55
import type { InboxItem } from '@/platform/app/(shell)/_components/model'
66
import { useSidebarLayout } from '@/platform/app/(shell)/_components/sidebar/sidebar-layout'
7-
import { Icon, SegmentedControl } from '@/platform/design-system/components'
7+
import { ActivityRow, Icon, SegmentedControl } from '@/platform/design-system/components'
88
import { AnchoredPopover } from '@/platform/design-system/components/menu/anchored-popover'
99

1010
interface InboxProps {
@@ -43,24 +43,17 @@ export function Inbox({ anchor, items, navigate }: InboxProps) {
4343
</header>
4444
<ul className={styles.list}>
4545
{visible.map((item) => (
46-
<li key={item.id}>
47-
<button
48-
type='button'
49-
className={styles.inboxRow}
50-
onClick={() => {
51-
setRead((previous) => new Set(previous).add(item.id))
52-
setOpen(false)
53-
navigate(item.href)
54-
}}
55-
>
56-
<Icon name={item.icon} />
57-
<span className={styles.copy}>
58-
<span data-type='T059'>{item.title}</span>
59-
<span data-type='T050'>{item.description}</span>
60-
</span>
61-
<Icon name='I032' />
62-
</button>
63-
</li>
46+
<ActivityRow
47+
key={item.id}
48+
leading={<Icon name={item.icon} />}
49+
title={item.title}
50+
detail={item.description}
51+
onOpen={() => {
52+
setRead((previous) => new Set(previous).add(item.id))
53+
setOpen(false)
54+
navigate(item.href)
55+
}}
56+
/>
6457
))}
6558
</ul>
6659
{!visible.length && (

0 commit comments

Comments
 (0)