Skip to content

Commit d077502

Browse files
committed
Keep platform navigation in place and unify Home and invite interactions
1 parent 5152ad4 commit d077502

61 files changed

Lines changed: 782 additions & 407 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.

‎apps/sim/platform/AGENTS.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,17 @@ Use `ControlRow` for the sidebar, identity controls, panel tabs, recent links, w
2828

2929
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.
3030

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 content and uses the dropdown’s popup padding and row insets.
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. Home widgets and notification panels use `--ui-widget-max-size` as a ceiling, not a forced height. Keep the dashboard free to scroll and let each overflowing list or schedule scroll independently. Plain tooltips preview at most three lines; retain the complete accessible text. A structured people popover owns its content and uses the dropdown’s popup padding and row insets.
3232

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. Textareas scroll or grow within their owner and never show a manual resize handle.
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. Opening a dialog or advancing its step focuses the first available field. Confirmation dialogs focus Cancel, and read-only previews focus Close. Closing returns focus to the invoking control. Textareas scroll or grow within their owner and never show a manual resize handle. Recipient fields use the textarea’s height, type and inset. Each email tag occupies one text line with the recent row’s trailing remove action. Comma or Enter commits a draft, pasting a list commits its addresses, and Backspace from an empty draft removes the last tag.
3434

3535
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.
3636

3737
Opening a side-by-side panel keeps the chat title at the same inline and block position. A full-width panel takes over the reading surface until the reader returns to chat. Run disclosure controls thinking and tool activity. Assistant prose stays visible when activity collapses. Each chat message owns a continuous hover area from its content through its action row. Its timestamp and actions stay available when navigating by keyboard and on touch. Chat thinking, tool summaries and prose paragraphs share one 12px block gap. Expanded payloads have equal block padding; the final message keeps a section of clearance from the composer. Render pasted text, files and images with ControlRow and a sibling remove action. 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.
3838

3939
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. Neutral quiet controls gain the hover surface without changing ink. Destructive and primary confirmations change perceived lightness by the same amount within each theme; check rendered colors, not blend percentages. Destructive menu commands turn red on hover or keyboard highlight. A compact × dismisses a tab, recent, draft attachment or recipient with the ordinary quiet IconButton. Callers choose intent, never paint. 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.
4040

41-
Use [the shared Platform time formatters](lib/time.ts) for displayed ages, clocks, calendar ranges and chart dates. Keep raw instants or ISO calendar days in data. Relative ages use a single reference instant for the surface: now below one minute, whole minutes below an hour, hours below 24 hours, then `1d ago` until 48 hours. After that show an ordinal date, including the year when it differs. Preserve calendar days across time zones. Pass the viewer’s zone for a locally composed message. Fixture time is server-gated and fixed for reproducible rendering. A Home activity chart keeps one visibility setting and one period across modes: Build shows Tasks run and Search shows Searches made. Both draw real dated daily counts; the axis, tooltip and Customize Home label must agree with the selected metric.
41+
Use [the shared Platform time formatters](lib/time.ts) for displayed ages, clocks, calendar ranges and chart dates. Keep raw instants or ISO calendar days in data. Relative ages use a single reference instant for the surface: now below one minute, whole minutes below an hour, hours below 24 hours, then `1d ago` until 48 hours. After that show an ordinal date, including the year when it differs. Preserve calendar days across time zones. Pass the viewer’s zone for a locally composed message. Fixture time is server-gated and fixed for reproducible rendering. A Home activity chart keeps one visibility setting and one period across modes: Build shows Tasks run and Search shows Searches. Both draw real dated daily counts; the axis, tooltip and Customize Home label must agree with the selected metric.
4242

4343
## Public API
4444

‎apps/sim/platform/README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ SIM_PLATFORM_FIXTURES=1 bun run --cwd apps/sim dev:platform
1313

1414
Open [Home](http://localhost:3100/home), [Chat](http://localhost:3100/chat/weekly-review?state=full), [Settings](http://localhost:3100/settings), or the separate [component workbench](http://localhost:3100/workbench). With the flag on, `?state=empty|full|stress` selects the fixture on any of them. `?theme=light|dark|system` selects the product appearance. Product navigation preserves these choices; nothing writes examples to storage. The URL cannot activate the server flag. Old catalog links with a recipe still open the workbench.
1515

16-
The product routes live under `app/(shell)/`. A persistent layout holds transient interface preferences, sidebar state and chat edits. Pages choose the server fixture and compose a few owners. Each route owns its components and CSS Modules in a private `_components/<component>/` folder: the [Home chart](app/%28shell%29/home/_components/activity-chart/activity-chart.tsx) sits beside its styles; the [dialog](design-system/components/modal/modal.tsx) belongs to EMCN. Shared shell UI lives in `app/(shell)/_components/`. [Rhythm tokens](design-system/styles/rhythm.css) and the [ownership and interaction contract](AGENTS.md) apply across all three routes. The [Paper state inventory](docs/core-chat-states.md) enumerates the source rows in scope. Integrations, workspace destinations and workspace create/sort return to Home; Wiki is deferred.
16+
The product routes live under `app/(shell)/`. Their shared App Router layout owns the shell, sidebar scroll viewport, interface preferences and chat edits. Home, Chat and Settings pages render their own bodies inside that layout. Navigating between them keeps the sidebar reading position, including when returning from Settings. The shell selects its gated fixture and appearance from the current URL. Each route owns its components and CSS Modules in a private `_components/<component>/` folder: the [Home chart](app/%28shell%29/home/_components/activity-chart/activity-chart.tsx) sits beside its styles; the [dialog](design-system/components/modal/modal.tsx) belongs to EMCN. Shared shell UI lives in `app/(shell)/_components/`. [Rhythm tokens](design-system/styles/rhythm.css) and the [ownership and interaction contract](AGENTS.md) apply across all three routes. The [Paper state inventory](docs/core-chat-states.md) enumerates the source rows in scope. Integrations, workspace destinations and workspace create/sort return to Home; Wiki is deferred.
1717

1818
## Desktop
1919

@@ -41,13 +41,13 @@ Shared and Platform still own different recipes; `catalog/evidence.json` preserv
4141

4242
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.
4343

44-
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. The resting Home greeting aligns with the sidebar Home row. The shell uses one 12px inset above the organization and on both sides of the profile row. The Home inset scrolls away through the viewport edge fade. The collapsed sidebar control aligns with the expanded toolbar. Calendar events share the muted button’s solid, borderless rest, hover and pressed fills; the event opens local details and attendees have a separate control. Provider navigation awaits real event URLs.
44+
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. The resting Home greeting aligns with the sidebar Home row. The shell uses one 12px inset above the organization and on both sides of the profile row. The Home inset scrolls away through the viewport edge fade. The collapsed sidebar control aligns with the expanded toolbar. Calendar events use the muted surface. An event with a provider URL opens in a new tab. The attendee control opens its own people popover. Now appears when the current time scrolls out of view or another day is selected. It restores today’s schedule and centers the current time.
4545

4646
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. The notification count uses the compact Invite button. Expanded notices share one subtle shadow around the list. A new notice pauses its expiry on hover or keyboard focus. Icon color transitions take 120ms and respect reduced motion. The [design contract](AGENTS.md) defines these rules.
4747

4848
### Time and Home activity
4949

50-
Home switches between **Tasks run** in Build and **Searches made** in Search, using the same selected 7-, 30- or 90-day window and visibility setting. Customize Home follows the mode. Hover and keyboard inspection show the date and that day’s actual metric. The fixture snapshot provides dated observations and a fixed reference time; fixture-off routes show no example activity.
50+
Home switches between **Tasks run** in Build and **Searches** in Search, using the same selected 7-, 30- or 90-day window and visibility setting. Customize Home follows the mode. Hover and keyboard inspection show the date and that day’s actual metric. The fixture snapshot provides dated observations and a fixed reference time; fixture-off routes show no example activity.
5151

5252
Import [Platform time formatting](lib/time.ts) for dates, relative ages and clock ranges. Mail, attention rows and file updates display `12m ago`, `1hr ago`, `17hrs ago`, `1d ago`, then dates such as `Nov 2nd` after 48 hours. The formatter includes the year for older years. Axes use short month/day labels; a calendar date never shifts because of the browser’s timezone.
5353

‎apps/sim/platform/app/(shell)/_components/command-menu/command-menu.tsx‎

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { PLATFORM_DESTINATIONS } from '@/platform/app/(shell)/_components/destinations'
12
import type { ShellData, ShellItem } from '@/platform/app/(shell)/_components/model'
23
import { CommandPalette } from '@/platform/design-system/components/command-palette/command-palette'
34
import { OverlayDialog } from '@/platform/design-system/components/modal/overlay-dialog'
@@ -17,12 +18,14 @@ export function CommandMenu({ open, onClose, navigate, chats, workspaces }: Comm
1718
label='Search the platform'
1819
onSelect={navigate}
1920
groups={[
21+
{ items: PLATFORM_DESTINATIONS },
2022
{
21-
items: [
22-
{ id: 'home', label: 'Home', icon: 'I104', value: '/home' },
23-
{ id: 'new', label: 'New chat', icon: 'I073', value: '/chat' },
24-
{ id: 'settings', label: 'Settings', icon: 'I116', value: '/settings' },
25-
],
23+
label: 'Workspaces',
24+
items: workspaces.map((item) => ({
25+
id: item.id,
26+
label: item.name,
27+
value: '/home',
28+
})),
2629
},
2730
{
2831
label: 'Chats',
@@ -33,14 +36,6 @@ export function CommandMenu({ open, onClose, navigate, chats, workspaces }: Comm
3336
value: `/chat/${item.id}`,
3437
})),
3538
},
36-
{
37-
label: 'Workspaces',
38-
items: workspaces.map((item) => ({
39-
id: item.id,
40-
label: item.name,
41-
value: '/home',
42-
})),
43-
},
4439
]}
4540
/>
4641
</OverlayDialog>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const PLATFORM_DESTINATIONS = [
2+
{ id: 'home', label: 'Home', icon: 'I104', value: '/home' },
3+
{ id: 'settings', label: 'Settings', icon: 'I116', value: '/settings' },
4+
{ id: 'integrations', label: 'Integrations', icon: 'I070', value: '/home' },
5+
] as const

‎apps/sim/platform/app/(shell)/_components/notification-tray/notification-tray.module.css‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@
99
var(--shell-notice-width),
1010
calc(100vw - var(--spacing-40) - env(safe-area-inset-left) - env(safe-area-inset-right))
1111
);
12-
max-block-size: calc(
13-
100dvh -
14-
var(--spacing-40) -
15-
env(safe-area-inset-top) -
16-
env(safe-area-inset-bottom)
12+
max-block-size: min(
13+
var(--ui-widget-max-size),
14+
calc(100dvh - var(--spacing-40) - env(safe-area-inset-top) - env(safe-area-inset-bottom))
1715
);
1816
gap: var(--ui-row-gap);
1917
pointer-events: none;

‎apps/sim/platform/app/(shell)/_components/organization-menu/organization-menu.tsx‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { type RefObject, useState } from 'react'
3+
import { type RefObject, useRef, useState } from 'react'
44
import type { ShellData } from '@/platform/app/(shell)/_components/model'
55
import styles from '@/platform/app/(shell)/_components/organization-menu/organization-menu.module.css'
66
import {
@@ -22,6 +22,7 @@ interface OrganizationMenuProps {
2222
export function OrganizationMenu({ anchor, organization, onSettings }: OrganizationMenuProps) {
2323
const [open, setOpen] = useState(false)
2424
const [invite, setInvite] = useState(false)
25+
const opener = useRef<HTMLElement>(null)
2526
const name = organization?.name ?? 'Organization'
2627
return (
2728
<>
@@ -30,7 +31,10 @@ export function OrganizationMenu({ anchor, organization, onSettings }: Organizat
3031
label='Organization menu'
3132
anchor={anchor}
3233
open={open}
33-
onOpenChange={setOpen}
34+
onOpenChange={(next, trigger) => {
35+
if (next && trigger instanceof HTMLElement) opener.current = trigger
36+
setOpen(next)
37+
}}
3438
trigger={(button) => (
3539
<IdentityButton
3640
render={button}
@@ -56,6 +60,7 @@ export function OrganizationMenu({ anchor, organization, onSettings }: Organizat
5660
size='compact'
5761
disabled={!organization}
5862
onClick={() => {
63+
opener.current?.focus({ preventScroll: true })
5964
setOpen(false)
6065
setInvite(true)
6166
}}
@@ -80,7 +85,7 @@ export function OrganizationMenu({ anchor, organization, onSettings }: Organizat
8085
confirmLabel='Invite'
8186
confirmDisabled
8287
>
83-
<RecipientField label='Email addresses' recipients={[]} placeholder='name@company.com' />
88+
<RecipientField label='Email addresses' name='recipients' placeholder='name@company.com' />
8489
</Modal>
8590
</>
8691
)

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
.shell[data-sidebar-collapsed] {
1010
grid-template-columns: minmax(0, 1fr);
1111
}
12+
.shell[data-sidebar-collapsed] > aside {
13+
display: none;
14+
}
1215
.shell[data-desktop] {
1316
grid-template-rows: auto minmax(0, 1fr);
1417
}

0 commit comments

Comments
 (0)