0. Design Principles
The memax design philosophy. Read this first — every token, component, and pattern decision flows from these 8 rules.
If you are an AI agent generating memax UI code, these are the hard rules. Each rule maps to a section. Violating any rule produces non-memax output.
MEMAX DESIGN SYSTEM — HARD RULES FOR CODE GENERATION ═══════════════════════════════════════════════════════ COLORS (§13) text: text-fg-1 (titles, body) | text-fg-2 (secondary) | text-fg-3 (meta) | text-fg-4 (decorative) surfaces: bg-surface-1 (subtle) | bg-surface-2 (hover) | bg-surface-3 (active) accent: var(--signature) — ONLY for AI indicators (✦), never decorative NEVER: text-gray-*, bg-gray-*, hardcoded colors, text-foreground/XX TYPOGRAPHY (§12) H1: text-[21px] font-bold (one per view, memory detail title) H2: text-[16px] font-bold (section headers) H3: text-[14px] font-semibold (card titles) body: text-[14px] text-fg-1 (default) secondary: text-[13px] text-fg-2 or text-fg-3 (descriptions, meta) caption: text-[12px] text-fg-3 (timestamps, category) micro: text-[10px] text-fg-4 uppercase tracking-wider font-semibold (group headers) weights: 400-700 only. NEVER font-light (300) or font-black (900) prose: leading-[1.65] for AI answers and readable paragraphs SURFACES (§14) card grid: <Surface variant="default"> (bar-border + bar-shadow) detail: <Surface variant="subtle" rounded="2xl"> (border only) mobile: <Surface variant="borderless"> (full bleed) minimal: <Surface variant="clean"> (bg only) radius: bar/modal 16px → card 12px → button 8px → nested 6px LAYOUT (§20) page: max-w-4xl mx-auto px-5 sm:px-8 pb-36 md:pb-32 top: paddingTop: CONTENT_TOP (80px desktop) entrance: animate-content-ready (on every page load) bar: fixed bottom center, z-50, max-w-[640px] modal: z-65 content, z-60 backdrop MOTION (§18) easing: var(--ease-spring) — cubic-bezier(0.16, 1, 0.3, 1) NEVER: linear, ease-in-out, ease fast: 0.15s (hover, modal, dropdown) normal: 0.2s (content transitions) AI breathe: state-slow-breathe (2.5s loop) entrance: animate-content-ready (0.15s opacity + translateY) CONTROLS (§19) buttons: <Button variant="default|outline|secondary|ghost|destructive"> sizes: xs (h-6) | sm (h-7) | default (h-8) | lg (h-9) icon: icon-xs (24px) | icon-sm (28px) | icon (32px) | icon-lg (36px) send btn: h-8 w-8 rounded-lg. Push=foreground fill, Recall=signature fill toggle: w-10 h-6 rounded-full. On=signature, Off=transparent+border ACCESSIBILITY (§27) fg-1: 12.2:1 — safe everywhere fg-2: 5.2:1 — safe everywhere fg-3: 2.5:1 — meta/labels only, FAILS AA normal text in light mode fg-4: 1.5:1 — decorative only, NEVER readable text focus: focus-visible:ring-3 focus-visible:ring-ring/50 touch: 44px primary, 32px secondary, 24px minimum
All text and surface colors derive from --foreground via opacity. Never hardcode color values in components.
Why: One variable change updates the entire palette. Dark mode, themes, and accessibility come free.
text-fg-2 (65% opacity) instead of text-gray-500The same container adapts to different states. Never overlay when the surface can transform.
Why: Overlays break spatial continuity. Users lose context when content jumps between surfaces.
Memory card morphs into detail view — no modal popup, no new page.Dream Violet (--signature) appears only where memax AI is actively working. Never decorative.
Why: If signature color is everywhere, it means nothing. Scarcity creates meaning.
✦ star breathes during AI streaming, static when complete. Never on borders or backgrounds.Floating bottom bar, centered content. Tree navigation is peek-on-demand, not always visible.
Why: Maximum content density. The bar is the only persistent UI element.
Bar: fixed bottom center (z-50). Content: scrolls freely with max-w-4xl.Use var(--ease-spring) for all transitions. Never linear, never ease-in-out.
Why: Spring easing (fast start, gentle settle) creates the memax feel — responsive, alive, never sluggish.
cubic-bezier(0.16, 1, 0.3, 1) for all UI motion.Skeletons mirror the final layout exactly. Loading states show shape, not spinners.
Why: The brain anchors to layout. When content loads into the same shape, it feels instant.
Shimmer skeleton with dot+title+body matches loaded MemoryRow.Use spacing and opacity to separate sections. Never add visible dividers or accent borders.
Why: Dividers add visual noise. Whitespace is a stronger (and quieter) grouping signal.
divide-border/20 (barely visible) or gap spacing, never border-l-4 colored accent.Default styles are mobile. Add sm:/md:/lg: for larger screens. Never block keyboard handlers behind isMobile.
Why: Touch targets and keyboard shortcuts coexist. isMobile is for visual hints only, never for disabling functionality.
Mobile: pill chips (44px touch). Desktop: text toggle. Both work with keyboard.No visual noise. Opacity hierarchy instead of color variety. Whitespace instead of dividers. The content breathes.
Spring easing on everything. ✦ breathes when AI works. Containers morph instead of swapping. Nothing feels static or dead.
One accent color, used sparingly. No gradients, no decorative elements. The product gets out of the way. Content is the hero.
text-gray-500text-fg-3Gray doesn't adapt to dark mode or themesease-in-outvar(--ease-spring)Feels sluggish and genericborder-l-4 border-blue-500Surface variant + shadow liftColored accent lines are not memaxbg-gray-100bg-surface-1Surface tokens adapt to any themeshadow-mdvar(--bar-shadow)Bar shadow is a composed, branded shadowrounded-md (6px) on cardsrounded-xl (12px)Cards use card radius, not component radiusModal overlay for editingInline transform / morphContainer morphing principleSpinner for loadingSkeleton shimmerShape before content principletext-foreground/40text-fg-3Use semantic tokens, not raw opacityfont-light (300)font-normal (400) minimumProduct uses 400-700 weight range onlyFoundations
Colors → Section 13
Typography → Section 12
Surfaces → Section 14
Spacing → Section 20
Motion → Section 18
Branding → Section 11
Components
Buttons/Badges → Section 19
Indicators → Section 17
State Machine → Section 16
Loading → Section 08
Empty States → Section 09
globals.css— all CSS custom properties (tokens)ui/button.tsx— Button (CVA variants + sizes)ui/badge.tsx— Badge (CVA variants)ui/surface.tsx— Surface (5 container variants)ui/tag.tsx— Tag (removable pill)ui/skeleton.tsx— Skeleton (loading placeholder)ui/memax-loader.tsx— MemaxLoader (signature dots)lib/motion.ts— JS timing constantslib/category.ts— Category dot colors11. Branding
Logo wordmark, icon at multiple sizes, and loader variants.
Light
Dark
48px
32px (favicon)
20px (nav)
48px dark
inline
compact
with label
3 dots in signature color, sequential pulse. Pure CSS — no requestAnimationFrame.
13. Color Tokens
Semantic color system built on oklch opacity architecture. All derived from --foreground, adapts to any theme or dark mode automatically.
--fg-1: oklch(from var(--foreground) l c h / var(--op-primary));
--surface-1: oklch(from var(--foreground) l c h / var(--op-bg-subtle));All semantic colors are computed from --foreground with opacity multipliers. Change one variable, entire palette adapts. No hardcoded color values in components.
text-fg-1Titles, body, input text
/90text-fg-2Descriptions, placeholders, secondary labels
/65text-fg-3Timestamps, meta, muted labels
/40text-fg-4Hints, annotations, divider text
/20bg-surface-1Slight tint, chip background, hover hint
/3bg-surface-2Hover state, selected row, active tab
/5bg-surface-3Active/pressed state, strong selection
/8Stacking demo
--background#fafafaoklch(0.155 0.01 260)Page background--foreground#1c1c1eoklch(0.985 0 0)Primary text base--card#ffffffoklch(0.2 0.008 260)Card, bar, modal surfaces--border#e5e5e5oklch(0.275 0 0)Card borders, separators--muted#f2f2f2oklch(0.269 0 0)Summary callouts, subtle bg--destructive#ff3b30oklch(0.704 0.191 22)Error, delete states--primary#000000oklch(0.922 0 0)Primary button, CTA fill--secondary#e8e8e8oklch(0.269 0 0)Secondary button, modal headerBackground
--backgroundCard
--cardForeground
--foregroundBorder
--borderMuted
--mutedDestructive
--destructivePrimary
--primarySecondary
--secondaryoklch(0.62 0.16 290)The memax intelligence marker. Appears only where memax is actively working — never decorative, never structural.
Allowed usage
Never use for
Decorative borders or backgrounds
Regular button fills or highlights
Text color for non-AI content
Branding outside the app
--signature-mutedoklch(signature / 0.12) — subtle tint for backgrounds
--bar-borderBar border color (12% opacity)--bar-shadowBar shadow (layered: outline + medium + large)--bar-bgBar background (falls back to --card)Source: lib/category.ts. Colors are frontend-only — backend classifies, frontend renders the dot. Dot size: 6px (w-1.5 h-1.5) in memory rows, 8px (w-2 h-2) in detail view.
12. Typography
Complete type system. Every text element maps to a CSS variable + Tailwind class. 7-step scale, 3-level heading hierarchy, 4 weights.
font-sansThe quick brown fox jumps over the lazy dog
All body text, UI labels, inputs. System font stack for native feel.
font-headingThe quick brown fox jumps over the lazy dog
Display headings, branding. Tight letter-spacing, geometric terminals.
font-monoconst memory = await memax.recall(query);
Code snippets, token names, technical values.
Settings
text-[21px] font-bold21px · 700 (bold)— Memory detail title, page header (one per view)Deployment Strategy
text-[16px] font-bold16px · 700 (bold)— Section header, note detail titleReact Server Components
text-[14px] font-semibold14px · 600 (semibold)— Card title, list item title, sub-sectionRule: one H1 per view. H2 for sections within a page. H3 for cards/list items. Never skip levels.
--text-title18pxtext-[18px]Modal title, page header--text-input15→16pxtext-[15px] sm:text-[16px]Bar input, placeholders--text-body14pxtext-[14px]Card title, body, AI answer--text-secondary13pxtext-[13px]Snippet, citation, button label--text-caption12pxtext-[12px]Category, timestamp, meta--text-micro11pxtext-[11px]Group header, keyboard hint--text-nano10pxtext-[10px]Citation badge, section label, DemoCard labelThe quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
Rule: never use font-light (300) or font-black (900). Product uses 400–700 range only.
fg-1Primary text — titles, body, input valuefg-2Secondary text — descriptions, placeholdersfg-3Tertiary text — timestamps, meta, labelsfg-4Decorative — hints, divider text, annotationsAll computed from --foreground via oklch opacity. Adapts automatically to dark mode. Never use raw foreground/XX — always use text-fg-N classes.
1.0leading-none1.4leading-tight1.5leading-snug1.65leading-[1.65]Server components render on the server and send HTML. Client components hydrate on the client.
Title: text-[14px] font-semibold text-fg-1 (H3)
Body: text-[13px] text-fg-2 leading-[1.65] line-clamp-2
Meta: text-[12px] text-fg-3 / text-fg-4
14. Surfaces
5-level elevation hierarchy + 5 Surface variants. Every container in the product uses one of these combinations.
Page canvas, scroll body. No border, no shadow.
Layout root, brain view scroll areaContent container. Subtle border (--border), no shadow.
Memory detail body (desktop), settings sectionsPrimary cards. Bar-border (12% opacity) + layered shadow.
Memory grid cards, topic cards, bar, floating panelsAbove-page overlays. Glass blur + frosted edge.
Dropdowns, popovers, command paletteFull-screen overlay with backdrop dimming.
MemoryModal, settings dialog, confirmationsRule: never skip elevation levels. Page → Content → Elevated → Floating → Modal.
<Surface variant="default">Memory grid, topic cards, bar<Surface variant="subtle">Memory detail body (desktop), inline containers<Surface variant="flat">Inline sections, nested containers<Surface variant="borderless">Mobile detail page, fullscreen views<Surface variant="clean">Minimal containers, collapsed sectionsh-14 · rounded-2xl · bg-card · bar-border · bar-shadowItem one
Description here
Item two (hover)
Same border/shadow as bar. Separator: border-t border-border/30. Hover: bg-surface-1.
16pxrounded-2xl12pxrounded-xl8pxrounded-lg6pxrounded-md9999pxrounded-fullRule: outer container always has larger radius than inner. Bar/modal 16px → card 12px → button 8px → nested 6px.
Glass communicates layer hierarchy: “this floats above that.” Never decorative.
Frosted edge (scroll top/bottom)
backdrop-blur(20px) saturate(1.4) · mask-image dissolveDissolve gradient (infinite canvas)
linear-gradient(to top, var(--card), transparent) · 120px20. Layout
Page widths, 8px spacing grid, responsive breakpoints, z-index hierarchy, and safe area insets. Every layout decision references this.
max-w-[640px]Global input bar. Centered, responsive width shrinks on larger screens.
w-[calc(100%-32px)] sm:w-[calc(100%-96px)] md:w-[calc(100%-128px)]max-w-4xl (896px)Memory grid, topic detail. Outer padding + inner max-width.
px-4 sm:px-8max-w-4xl (896px)Same max-w as list — container morphs, no new surface.
px-5 sm:px-8 md:px-10880pxCentered dialog. Sidebar nav (desktop), tabs (mobile).
sidebar 220px + content px-8max-w-sm (384px)Single card, centered. PWA share sheet capture.
p-54pxgap-1Micro gaps (icon-to-icon, badge internal)8pxgap-2Inline gaps (icon + text, dots)12pxgap-3Tight element spacing (badge group)16pxgap-4Card padding, grid gap, section gaps24pxgap-6Section spacing within a page32pxgap-8Section margin, page top padding48pxgap-12Large section breaks, page bottom64pxgap-16Page-level spacing (rarely used)(default)0px — 639pxSingle column, pill chips, bottom bar, borderless surfaces
sm:640px — 767px2-column grid, text mode toggle, larger type
md:768px — 1023px3+ column grid, sidebar peek, full bar width
lg:1024px — —Pinned sidebar, max content width, settings modal
Rule: mobile-first. Default styles = mobile. Add sm: / md: / lg: for larger screens. Never hide critical functionality behind a breakpoint.
z-65z-60z-50z-10z-[3]z-[2]z-[1]Rule: bar is z-50, modal is z-60+. Content lives at z-[1-3]. Never use arbitrary z values — pick from this list.
--safe-topStatus bar inset (iOS notch)--safe-bottomHome indicator (iPhone), bar clearance--safe-leftLandscape notch (left)--safe-rightLandscape notch (right)padding-bottom: calc(var(--safe-bottom) + 16px);Bar uses safe-bottom for iPhone home indicator clearance. Always add safe area to fixed bottom elements.
z-[2]: content
z-50: bar
Content scrolls behind bar. Bar is fixed bottom center. No sidebar, no top bar. Bottom padding: safe-bottom + bar height + 16px clearance.
18. Motion Tokens
Named timing constants + easing curves. Every animation references a token — never raw durations. Spring easing is the default.
INSTANT0.1s
UI state changes — intent label swap, right slot toggle, hover feedback
opacity: 0 → 1 on hoverFAST0.15s
Modal open/close, expand slot, view switch, dropdown appear
scale: 0.96 → 1 on mountNORMAL0.2s
Content dissolve, list reorder, layout shift, page transition
translateY: 6px → 0 on enterLOADING0.5s
Breathing dots, loading pulses, skeleton shimmer
opacity: 0.3 → 1 loopSIGNATURE3.5s
Placeholder cycling, ghost breathing, slow atmospheric loops
crossfade placeholder textCSS: var(--duration-*) · JS: import { INSTANT, FAST, NORMAL, LOADING, SIGNATURE }from "@/lib/motion"
0.1s0.15s0.2s0.5s3.5scubic-bezier(0.16, 1, 0.3, 1)Default for all UI transitions. Fast start, gentle settle. Apple-like feel.
Use for: Everything unless specified otherwise
cubic-bezier(0.34, 1.56, 0.64, 1)Slight overshoot then settle. Playful, attention-drawing.
Use for: Toast enter, success indicator, notification badge
Rule: never use linear or ease-in-out. Spring easing is the memax feel. Set it once in Framer Motion transition or CSS transition-timing-function.
Red = forbidden easings. Notice how linear feels robotic and ease-in-out feels sluggish compared to spring.
animate-content-readySkeleton → content transition. Opacity 0→1 + translateY 6px→0.
0.15s— All loading → loaded state changes globallystate-slow-breatheGentle opacity pulse 0.3→1. For AI processing indicators.
2.5s infinite— ✦ star during AI streaming, "organizing..." textstate-fast-pulseQuick opacity pulse. For active processing feedback.
1s infinite— Category dot during processing, inline loadinganimate-fade-upEntrance: opacity 0→1 + translateY 8px→0.
0.3s— Section entrance, staggered list itemsMemaxLoaderSequential pulse dots in signature color. Brand loading.
per-dot stagger— Full-page cold start, route transitionslow-breathe: AI streaming/processing. fast-pulse: active card processing. static: complete/idle.
Crossfade cycle during AI loading. Source: RecallingText component. verb breathes in signature color.
Never use raw durations — always reference a token (CSS var or JS constant)
Spring easing everywhere — never ease-in-out, never linear
Signature color only breathes — never flashes, never blinks
No animation > bad animation — if unsure, use a simple opacity fade
prefers-reduced-motion — respect system setting, disable non-essential motion
27. Accessibility
WCAG contrast ratios (calculated from real tokens), focus management, reduced motion, touch targets, color blindness. Hard rules, not guidelines.
fg-1/9012.2:112.7:112.9:1PASSPASSUse for all body text, titles, headings, input values. No restrictions.fg-2/655.2:15.3:17.3:1PASSPASSSafe for descriptions, placeholders, secondary labels. Passes AA at all sizes.fg-3/402.5:12.5:13.7:1FAILFAILDECORATIVE ONLY in light mode. Timestamps, meta, category labels. Never for readable body text. Dark mode passes AA large (3.7:1).fg-4/201.5:11.5:11.9:1FAILFAILNON-TEXT ONLY. Hints, annotations, divider text, decorative labels. Never for any content a user needs to read.WCAG 2.1 AA requirements:
Normal text (<18px): minimum 4.5:1 contrast ratio
Large text (≥18px bold or ≥24px): minimum 3.0:1 contrast ratio
Non-text (icons, borders): minimum 3.0:1 contrast ratio
4.8:1AA large PASS3.3:1AA large PASSSignature passes AA large text (3:1) in both modes. Safe for ✦ indicators (typically 12-20px) and button fills (white text on signature bg). Not safe for small body text in light mode.
fg-1Any size, any weight. No restrictions. (12.2:1+)
fg-2Any size, any weight. Safe everywhere. (5.2:1+)
fg-3Minimum 12px. Use for timestamps, meta, labels — never for content users must read. (2.5:1 light, 3.7:1 dark)
fg-4Decorative only. Annotations, DemoCard labels, divider dots. Never for readable text. (1.5:1+)
Known trade-off: fg-3 fails WCAG AA for normal text in light mode. This is intentional — fg-3 is for supplementary metadata (timestamps, category labels) where the information is also conveyed by position, grouping, or icon. If the text must be readable standalone, use fg-2.
focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:border-ring3px ring at 50% opacity of --ring. Border also changes to --ring.focus-visible:border-destructive/40 focus-visible:ring-destructive/20Red-tinted ring for destructive actions.focus:border-ring focus:ring-2 focus:ring-ring/502px ring (smaller than button). Border transitions to --ring.Browser default (no custom ring)Relies on native focus indicator. TODO: add visible ring.focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:roundedSubtle 2px ring with rounded corners.Focus rules:
1. Always use focus-visible, never focus — keyboard users only, not mouse clicks
2. Ring color is --ring at 50% opacity — visible but not aggressive
3. Tab order follows visual layout. No custom tabIndex unless absolutely necessary
4. All interactive elements must be reachable via Tab key
Tab through to see focus rings. Ring is always 3px (buttons) or 2px (inputs) with --ring at 50%.
When prefers-reduced-motion: reduce is set, these animations are disabled:
animate-content-ready → instant opacity (no transform)
animate-fade-up → removed
state-fast-pulse → static (no pulse)
state-slow-breathe → static (no breathe)
Framer Motion respects useReducedMotion() automatically. CSS-only animations use the media query in globals.css (line 1016).
What stays:
Color transitions (opacity, background) — these are non-motion
MemaxLoader dots — reduced to static display (no sequential pulse)
Placeholder text crossfade — kept but instant (no blur transition)
44×44pxPrimary actions: send button, mode pills, nav items, toggle switches
36×36pxSecondary actions: icon buttons (icon-lg), close buttons
32×32pxTertiary: icon buttons (default), copy buttons, tag remove
24×24pxMinimum: icon-xs buttons. Only for non-critical, supplementary actions
WCAG 2.5.8: minimum 24×24px target. Apple HIG: 44×44pt for primary actions. Memax follows Apple for primary, WCAG for minimum.
Category dots use 13 distinct hues — not distinguishable by color alone
Every dot is accompanied by a text label (category name) — color is supplementary
Red (destructive) is always paired with text or icon (Trash2), never color-only
Signature (violet) vs error (red) — sufficiently different hue for deuteranopia
Never convey meaning through color alone — always pair with text, icon, or shape
15. Palette Explorer
Interactive theme preview. Switch presets to compare text hierarchy, surfaces, and accents live.
Purple-blue inspired by dreams. Distinctive, creative, memax's signature feature.
/90Memory card title
/52Last recalled 3 hours ago from CLI
/40core / api-design / 2024-03-15
/25Content hash: 8f3a...
Readability test on card surface
Secondary text should be clearly readable without straining. If you squint to read this, the opacity is too low.
Tertiary text recedes but stays legible — timestamps, categories, metadata.
Muted text: placeholders, disabled labels, hashes.
| Level | Safe Neutral | Warm Apricot | Cool Slate | Earth Copper | Dream Violet |
|---|---|---|---|---|---|
| Primary | 0.9 | 0.9 | 0.9 | 0.92 | 0.9 |
| Secondary | 0.5 | 0.5 | 0.52 | 0.55 | 0.52 |
| Tertiary | 0.38 | 0.4 | 0.4 | 0.42 | 0.4 |
| Muted | 0.25 | 0.25 | 0.25 | 0.28 | 0.25 |
oklch(0.50 0 0)oklch(0.72 0.14 60)oklch(0.65 0.10 240)oklch(0.62 0.14 45)oklch(0.62 0.16 290)Safe Neutral (pure achromatic)
Dream Violet tint
REST endpoints should use plural nouns. Pagination via cursor, not offset.
We chose River over SQS because Postgres-backed queues simplify our infra. Trade-off: no cross-region fan-out, but we don't need it at current scale.
oklch(0.65 0.18 35)oklch(0.82 0.16 85)oklch(0.72 0.10 155)oklch(0.55 0.08 260)oklch(0.52 0.14 310)oklch(0.58 0.06 50)oklch(0.62 0.22 25)2026 trend: warm-tinted grays (Linear, Notion) + bold single accent (Superhuman 0.18, Vercel 0.21). Category dots unchanged — data layer, not UI chrome.
19. Controls
Complete interactive control gallery. All variants rendered from production components (ui/button.tsx, ui/badge.tsx, ui/tag.tsx).
<Button variant="default|outline|secondary|ghost|destructive|link">
Source: ui/button.tsx — CVA variants, @base-ui/react primitive
Use data-icon="inline-start|inline-end" for padding adjustment
<Badge variant="default|secondary|outline|destructive|ghost">
Source: ui/badge.tsx — h-5, rounded-4xl, text-xs
<Tag onRemove={fn}>label</Tag>
Source: ui/tag.tsx — bg-surface-1, border-border, rounded-lg, × on hover
h-8 w-8 rounded-lg. Push: foreground fill. Recall: signature fill. Source: bar-right-portal.tsx
Off: transparent track, foreground/15 border. On: signature fill, white knob.
Track: w-10 h-6 rounded-full. Knob: w-4.5 h-4.5 rounded-full.
Optimistic: switch moves immediately. Pending: border pulses until server confirms (600ms simulated).
h-8, rounded-lg, border-border, focus: border-ring + ring-2 ring-ring/50
Desktop: text "remember · recall" (dot separator). Mobile: pill chips with 44px min touch target. Source: layout.tsx mode toggle
Visible to all team members
This action cannot be undone
Pattern: label+description left, action button right. Separator: border-t border-border/20.
17. Visual Vocabulary
Two symbols only: ● dot (content) and ✦ star (memax intelligence). Behavior modifiers signal state.
Star uses signature color: oklch(0.62 0.16 290)
Star font-size ≈ 1.5× dot diameter for equal visual weight (star glyphs have more whitespace than filled circles).
16. State Machine
Content states + activity indicators. No ambiguous states.
LOADING ──→ LOADED ──→ UPDATING ──→ LOADED
│ │ │
│ ├──→ DELETING ──→ (removed)
│ │
├──→ EMPTY ├──→ ERROR
│ │ │
└──→ ERROR └─────────┘ (retry → LOADING)
Separate track:
LOADED ──→ PROCESSING ──→ LOADED (auto-poll)Reusable liveness indicator for agents, services, connections. Derived from timestamps (last_used, last_seen) — no polling, no websocket. Green pulses to draw attention; others are static.
Usage
Activity status rules
active— last_used <24h. Green + animate-ping pulse.
idle — last_used 1-7d. Amber, static.
inactive— >7d or never. Muted fg-4, static.
error — connection failure or expired key. Destructive red, static.
Production: lib/agents.ts (StatusDot in agent-configs-section.tsx). Extend for service health, MCP status, sync state.
8. Loading States
Shape before content. Signature color only when memax is actively working.
Loading your memory...
MemaxLoader (signature-colored sequential pulse dots) is the brand. Centered on background, not card. Text at /40. Used on brain view during initial load.
Brief transition. Compact loader, no text — user already knows where they're going.
AI answer uses summary callout pattern (bg-surface-1 rounded-xl). Entry: animate-content-ready. ✦ breathes (state-slow-breathe) while streaming, static when complete. Text at /75.
Skeleton mirrors loaded card: dot+title, body, meta. Shimmer uses foreground opacity, not gray — adapts to dark mode.
Recall loading: send button shows spinner (no notification banner). AI synthesis loading: ✦ breathing + RecallingText variant="ai" crossfade below results ("memax is thinking" → "reading your memories" → "connecting the dots"...).
0.15s ease-out, opacity 0→1 + translateY 6px→0. Applied globally to all loading→loaded transitions. Click to replay.
Use multi-stage builds to keep production images small. Separate build dependencies from runtime...
Card appears immediately with content. ✦ replaces category dot during processing. "organizing..." breathes in signature color.
9. Empty States
Three tiers: first-time onboarding, filtered no-results, and transient empty.
No memories yet
Type anything and press Enter to remember
Remember something →No memories in code
No reviews pending
No matching memories found.
Try different words or rephrase your question.
i18n: recall.noResults, recall.noResultsHint. Shown inside bar expand slot when recall returns 0 results. No icon — text only.
i18n: empty.firstTime.title, empty.firstTime.subtitle, empty.firstTime.cta, empty.filtered.title, empty.filtered.cta, empty.transient, recall.noResults, recall.noResultsHint
28. Async Action Pattern
Universal mutation lifecycle: Idle → Pending → Success/Error → Idle. MutationCache global handlers with meta-driven feedback. Zero per-callsite wiring.
MutationCache on QueryClient handles ALL mutation feedback globally.
meta.successMessage — static string or (data, vars) => string for dynamic messages.
meta.errorMessage — same signature. Shown for 5s with dismiss.
meta.skipGlobalToast — for mutations with custom UX (push flow with undo).
Bridge: module-level callback ref (mutation-toast.ts) wired by BarProvider on mount.
- Add
meta: { errorMessage: t.toast.* }to every mutation — even if optimistic rollback handles the UI. - Add
successMessageonly for irreversible or batch actions (disconnect, batch delete). - Use
skipGlobalToast: trueonly when the mutation has its own undo-capable feedback. - Callsite callbacks are for UI side-effects only (selection.exit(), navigation) — never for toasts.
- All messages go through i18n (
t.*). Dynamic messages useinterpolate(). useBarToast()is ONLY for non-mutation feedback (clipboard copy, DnD drop). Never use it inside mutation callbacks — MutationCache handles that.
| Hook | Success | Error |
|---|---|---|
| useBatchDelete | dynamic (count) | t.batch.forgetFailed |
| useBatchMoveToTopic | dynamic (count) | t.batch.moveFailed |
| useBatchMoveToHub | dynamic (count) | t.batch.moveFailed |
| useCreateMemory | silent | custom |
| useUpdateMemory | silent | t.toast.updateFailed |
| useDeleteMemory | silent | t.toast.deleteFailed |
| useShareMemory | t.toast.shared | t.toast.shareFailed |
| useDisconnectAgent | t.toast.disconnected | t.toast.disconnectFailed |
| useUpdateAgent | silent | t.toast.updateFailed |
| useDeleteAgentConfig | silent | t.toast.deleteFailed |
| useRevokeApiKey | silent | t.toast.revokeFailed |
| useCreateTopic | silent | t.toast.topicFailed |
| useUpdateTopic | silent | t.toast.topicFailed |
| useDeleteTopic | silent | t.toast.topicFailed |
| useAddMemoryToTopic | silent | t.toast.topicFailed |
| useRemoveMemoryFromTopic | silent | t.toast.topicFailed |
| useResolveReview | silent | t.toast.reviewFailed |
| useUpdateSettings | silent | t.toast.settingsFailed |
| useDreamTrigger | silent | t.toast.organizeFailed |
26. Composition Recipes
Full-page assembly from tokens → components → layout. Copy the structure, swap the content. Every class is annotated with its source section.
Server components render on the server...
Blue-green with canary releases...
Moving to JWT with refresh rotation...
max-w-4xl · px-5 sm:px-8
pb-36 md:pb-32 (bar clearance)
bar: z-50, fixed bottom center
Container: max-w-4xl mx-auto px-5 sm:px-8 pb-36 md:pb-32
Group header: text-[10px] text-fg-3 uppercase tracking-wider font-semibold
Memory row: px-4 py-2.5 border-t border-border/30 hover:bg-surface-1
Topic card: Surface variant="default" p-3
Bar: fixed bottom center, z-50, rounded-2xl, bar-border + bar-shadow
React Server Components
Server components render on the server and send HTML. Client components hydrate on the client. The boundary is the "use client" directive.
borderless — no Surface card
provenance strip: agent + captured + age + recalled
summary: text-fg-1 hero, ✦ signature
raw content: collapsed disclosure, text-fg-2
dissolve into --background
Borderless — no Surface card. All on --background.
Provenance: agent icon + name + captured + project + age + recalled N×
Summary: text-fg-1 hero, ✦ in signature. Raw content: collapsed disclosure, text-fg-2.
Sections: border-t border-border/20 + mt-5 pt-5. All content pl-8.
Dissolve: linear-gradient(to top, var(--background), transparent)
General
Visible to team members
System, Light, or Dark
Nightly consolidation
z-60: backdrop rgba(0,0,0,0.15)
z-65: modal 880px centered
sidebar: 220px, border-r border-border/30
rows: divide-y divide-border/20
Modal: 880px centered, z-65, rounded-2xl, bar-border + bar-shadow
Backdrop: z-60, rgba(0,0,0,0.15)
Sidebar: 220px, border-r border-border/30
Active tab: bg-surface-2 text-fg-1 font-medium
Rows: divide-y divide-border/20, py-3
Structured code blocks for the most common patterns. Copy directly into new components.
<div className="w-full px-4 py-2.5 hover:bg-surface-1 transition-colors border-t border-border/30">
<div className="flex items-center gap-2">
<span className="h-1.5 w-1.5 rounded-full" style={{ background: dotColor }} />
<span className="text-[14px] font-medium text-foreground truncate flex-1">{title}</span>
<span className="text-[13px] text-fg-3 tabular-nums">{meta}</span>
</div>
<p className="text-[13px] text-fg-3 truncate mt-0.5 pl-5">{summary}</p>
</div><div>
<div className="flex items-center gap-1.5 mb-3">
<span className="text-[12px]" style={{ color: "var(--signature)" }}>✦</span>
<span className="text-[13px] text-fg-3">memax summary</span>
</div>
<div className="text-[16px] text-fg-1 leading-[1.65]">{summary}</div>
</div><div className="flex items-center justify-between py-3">
<div>
<span className="text-[14px] text-fg-1">{label}</span>
<p className="text-[12px] text-fg-3">{description}</p>
</div>
<Button variant="outline" size="sm">{action}</Button>
</div><div className="pb-36 md:pb-32 animate-content-ready" style={{ paddingTop: CONTENT_TOP }}>
<div className="mx-auto max-w-4xl px-5 sm:px-8">
{/* Page content */}
</div>
</div><span className="text-[10px] text-fg-3 uppercase tracking-wider font-semibold">
{groupLabel}
</span><div className="flex flex-col items-center justify-center text-center" style={{ minHeight: CENTERED_HEIGHT }}>
<p className="text-[16px] text-fg-2 mb-2">{message}</p>
<p className="text-[13px] text-fg-3">{hint}</p>
</div>Page layout
width: max-w-4xl (896px)
padding: px-5 sm:px-8
bottom: pb-36 md:pb-32
top: CONTENT_TOP (80px)
entrance: animate-content-ready
Surfaces
grid card: Surface variant="default"
detail: borderless (no Surface)
section sep: border-t border-border/20
row hover: hover:bg-surface-1
row divider: border-t border-border/30
Typography
H1: text-[21px] font-bold
H2: text-[16px] font-bold
H3: text-[14px] font-semibold
body: text-[14px] text-fg-1
meta: text-[12px] text-fg-3
Motion
easing: var(--ease-spring)
fast: 0.15s (modal, hover)
normal: 0.2s (content, layout)
enter: animate-content-ready
ai: state-slow-breathe
1. Knowledge Space
Unified MemoryRow component used across all surfaces. One component, surface-driven presets. Attribution tiers: agent (Bot) → team (avatar) → personal (dot). Kitchen defines the spec, production implements it.
Every memory row: title (bold) + optional summary (muted). No tags at row level. Indicator + meta area adapt by surface/attribution. Hub label in right meta for team memories. Edge-to-edge within card (px-4), no rounded rows, flat dividers.
Zero-downtime deploys using blue-green with Fly.io machines...
If category is fully removed, what anchors the left side? Comparing: title-only, content-type icon, and tags-inline. Agent/team attribution tiers are unaffected (they already don't use category).
A. Title only — no indicator
Zero-downtime deploys using blue-green with Fly.io machines...
Push to staging branch triggers auto-deploy...
B. Content-type icon — visual anchor, no label
Zero-downtime deploys using blue-green with Fly.io machines...
Push to staging branch triggers auto-deploy...
Neutral dot for text, FileText for PDF, ImageIcon for image, LinkIcon for link. All foreground/20 (no category color).
C. First tag as context label — replaces subcategory
Zero-downtime deploys using blue-green with Fly.io machines...
Uses first tag as context label. More specific than category but varies wildly. Only works if tags are consistently short.
The indicator slot answers “who pushed this?” — not “which category.” Three tiers in priority order. Category dots remain as fallback for personal default only.
Moved chunking from handler to dedicated worker, added retry logic...
OAuth2 refresh token rotation with 30-day expiry...
Cohere rerank v3 at $1/1000 queries, 50ms p95...
Zero-downtime deploys using blue-green with Fly.io machines...
Two layout options for team hub memories in the personal “all” view. Compact: inline context, hub in right meta. Clean: attribution line above, title always left-aligned, verbs describe the action.
Compact — inline context, hub in meta
Zero-downtime deploys using blue-green with Fly.io machines...
OAuth2 refresh token rotation with 30-day expiry...
Moved chunking from handler to dedicated worker, added retry logic...
Dense, scannnable. Hub in right meta (11px, /25). Titles slightly misaligned due to varying context label width.
Clean — attribution above, verbs, titles aligned
Titles always left-aligned. Verbs: pushed (human), captured (agent). Attribution line only for team memories. Personal rows stay single-line. Taller rows but clearer hierarchy.
When no agent/team attribution, the indicator shows content type. Color comes from category (may be deprecated — dot color is the only remaining category signal).
Zero-downtime deploys using blue-green with Fly.io machines...
Push to staging branch triggers auto-deploy...
System diagram from Monday standup...
Official guide for multi-region Postgres with read replicas...
Text → colored dot · PDF → FileText icon · Image → ImageIcon · Link → LinkIcon. All colored by category dot color.
Same memory rendered on each surface. Surface controls which lines appear, which meta is shown, and whether copy is available.
Recent — full meta + copy
Zero-downtime deploys using blue-green with Fly.io machines...
Inbox — minimal, just title + age
Topic — summary focus, no source
Zero-downtime deploys using blue-green with Fly.io machines...
List — source + count + age
Zero-downtime deploys using blue-green with Fly.io machines...
Recall — summary focus, no source/count
Zero-downtime deploys using blue-green with Fly.io machines...
Universal across all surfaces. ✦ breathes with signature color. No summary, no tags, no meta — just title + status.
Zero-downtime deploys using blue-green with Fly.io machines...
Full layout: page header → Recent section (Clock, dropdown time filter, “Copy N for AI”) → topic cards grid → inbox (Inbox icon, count). Gap-3 consistent spacing.
Your Knowledge
147 memories · 6 topics
Zero-downtime deploys using blue-green with Fly.io machines...
Push to staging branch triggers auto-deploy...
Will be organized in the next dream cycle
In team hub, rows show WHO pushed WHAT. Same MemoryRow component, same surface="recent" — attribution tier auto-selects based on memory data.
OAuth2 refresh token rotation with 30-day expiry...
Cohere rerank v3 at $1/1000 queries, 50ms p95...
Moved chunking from handler to dedicated worker, added retry logic...
Inbox behavior differs by plan. Free users wait for nightly dream cycle. Pro users can trigger organization immediately. During organization, ✦ breathes and rows show progress.
Free — wait for dream cycle
Will be organized in the next dream cycle
Pro — organize now
3 memories waiting — organize now or let the dream cycle handle it
Organizing — dream triggered
Inside a topic. Breadcrumb → header with AI description → subtopic sections → “Other” section for ungrouped memories. No category tabs (category deprecated from UI). Rows use surface="topic": summary visible, edge-to-edge (px-4, no wrapper padding).
Deployment
23 memories · 3 subtopics
Blue-green deploys, rollback procedures, staging automation, and CI/CD pipeline configuration for Fly.io.
Zero-downtime deploys using blue-green with Fly.io machines...
Push to staging branch triggers auto-deploy...
Official guide for multi-region Postgres with read replicas...
System diagram from Monday standup...
Leaf topic (no subtopics). Description shown under header. Memories in a single flat card. No category tabs.
Go Patterns
31 memories
Error handling conventions, context propagation rules, interface design principles, and goroutine lifecycle patterns.
Zero-downtime deploys using blue-green with Fly.io machines...
Push to staging branch triggers auto-deploy...
Official guide for multi-region Postgres with read replicas...
System diagram from Monday standup...
Cards show AI description for leaf topics, subtopic names for parents. No category dots. Description comes from dream engine. h-full + flex-col pins age to bottom for consistent card height.
Leaf + description
Parent + subtopics
Pinned leaf
Empty (new)
Cards are the right container. Problem: current cards look generic. Fix: category dot DNA, AI description always visible, ✦ for dream signal, content preview for scent. Three options below.
A. Content-dense — description + memory preview + category dots
Blue-green deploys, rollback procedures, staging automation
Error handling, context propagation, interface design
React Server Components, Tailwind, Radix primitives
B. Compact — dots lead, description, sub-topics as · text
Blue-green deploys, rollback procedures
Staging · Production · Rollback
OAuth2 refresh flow, token rotation
Error handling, context propagation
Connection pooling, migration strategy
RSC, Tailwind, Radix primitives
Components · Styling
Onboarding, code review guide
C. Dream accent — left-edge gradient signals AI-organized
Blue-green deploys, rollback procedures
Error handling, context propagation
RSC, Tailwind, Radix primitives
memax identity signals:
• Category dots = memax's visual DNA (not emoji)
• ✦ + dream-purple = AI organized this, not the user
• Memory preview = content scent (what's IN the topic)
• Description = dream engine output
• No timestamps — recent activity via ✦ preview
Pre-dream (waiting)
Topics appear after your first dream
memax will organize your memories by subject during the nightly dream cycle.
Inbox clean
Your memory is clean
Dreams disabled
Dreams are turned off
Enable dreams to organize your knowledge.
Unified MemoryRow: One component across all surfaces. surface prop controls which lines/meta appear. Attribution tier auto-selects from memory data (sourceAgent → authorName → content type dot).
Category deprecation: Subcategory labels removed from rows and cards. Category dots remain only as indicator color fallback for personal tier. Topics + tags provide better navigation. Category tabs removed from topic detail.
Topic cards: Leaf topics show AI description (from dream engine). Parent topics show subtopic names with chevrons. No category dots. h-full + flex-col pins age to bottom for consistent grid height.
Topic detail:Description under header (from dream engine). Subtopic groups + “Other” section for ungrouped memories. No category tabs. Edge-to-edge rows (px-4, no px-1.5 wrapper).
Hub attribution:Hub name lives in the RIGHT meta area (text-[11px] foreground/25) so titles stay aligned. Only in personal “all” view. Hidden when switched to a specific hub. Personal memories never show hub label. Recall results should also show hub name for cross-hub results.
Rows: Edge-to-edge within card (px-4). No rounded-lg. Flat dividers (border-t border-border/30). hover:bg-surface-1.
Copy:Per-row copy (markdown) on Recent surface only. Section-level “Copy N for AI” (context block format) on section headers + topic header.
2. Memory Detail & Recall
Borderless reading surface. No card wrapping content — like Notion, Linear, Bear. Title → provenance → AI summary → collapsed raw content → meta footer → topic siblings. Whitespace and faint lines create structure.
React Server Components render on the server and send HTML to the client. Client Components hydrate and become interactive. The boundary is the "use client" directive.
This enables zero-JS for static content while maintaining interactivity where needed. Data fetching happens at the component level with async/await.
Entirely on --background. No Surface card. No border. No rounded container.
Sections separated by border-t border-border/20 + mt-5 pt-5 (whitespace + faint line)
All content pl-8 (aligned past back chevron)
Summary: text-fg-1 hero. Raw content: text-fg-2 collapsed. Tags/category: bottom.
Met with Sarah about the Q3 roadmap. Key takeaways: focus on performance first, then features. Ship the caching layer before the new dashboard.
No summary → content at text-fg-1 directly. No ✦, no disclosure. No topic → no siblings. recalled 0× → hidden. Cleanest possible page.
Still remembering...
memax is organizing this memory
Processing: ✦ slow-breathe centered on --background. Provenance visible. No Surface card needed.
Skeleton mirrors the borderless layout exactly: back button area → provenance line → content lines → footer tags. All on --background. Uses bg-foreground/[0.07] + animate-pulse with staggered delays. Transitions to loaded via animate-content-ready.
Couldn't load this memory
Check your connection and try again
Error: back button visible (escape route). Centered message + retry on --background. Red dot (destructive), text-fg-2 message, text-fg-3 hint, ghost retry button.
Skeleton → content uses animate-content-ready on the content wrapper. 0.15s opacity 0→1 + translateY 6px→0.
// Pattern:
{isLoading ? (
<DetailSkeleton />
) : (
<div className="animate-content-ready">
// loaded content
</div>
)}
MANDATORY for every loading→loaded swap. No pop-in without animation. Skeleton shapes must match loaded layout exactly.
MemoryStickyHeader component. Fixed z-40, fades in on scroll via IntersectionObserver. mask-image dissolve at bottom. Shows recall count when ≥10 (activity signal in compact form). Extracted as reusable component.
Default
Confirming
Forget left (destructive, requires movement), Keep right (safe, cursor already there). Navigation cancels.
Server Components render on the server. Client Components hydrate on the client. The boundary is "use client".
Mobile: identical design, not "narrower desktop." Back above title. No pl-8 (no back button inline). Provenance wraps. Same borderless surface as desktop now.
0=hidden, 1-9=fg-3, 10-49=fg-2 medium, 50+=fg-1 medium. Typographic weight only, no color.
Borderless. No Surface card. Entire page is --background. Whitespace + faint border-border/20 lines separate sections. Same on desktop and mobile.
Provenance first. After title: WHO captured, FROM WHERE, how often recalled. This is memax, not a note app.
Summary = hero. AI distillation gets text-fg-1. Raw content collapses at text-fg-2.
No summary → no split. Content renders directly. No ✦, no disclosure. Simplest case.
Classification at bottom. Category + tags + boundary in footer. Top = provenance. Middle = content. Bottom = organization.
Not a dead end. Topic pill → /topics/[id]. Siblings show 2-3 neighbors. Tags filterable.
3. Forget Experience
Container morphs from neutral to glassmorphism destructive tint. No modal — the surface transforms in place. Three states: normal, forgetting, forgotten.
Normal
Forgetting
Forgotten
Card — Normal
React Server Components render on the server and stream HTML to the client.
Card — Forgetting
React Server Components render on the server and stream HTML to the client.
Card — Forgotten
React Server Components render on the server and stream HTML to the client.
25. Batch Operations
Dot → check morph + glass toolbar. Entry via “Select” in section header trailing slot. Every action is end-to-end.
Each section header has a trailing slot (right side, after spacer). “Select” lives there as subtle text. Tapping it enters selection mode — dots morph to rings, toolbar appears. “Done” exits. Same pattern on every surface.
Alternative entries: long-press (mobile, 500ms), shift+click (desktop range select). Both auto-enter selection mode.
The h-3 w-3 indicator container morphs with spring easing. No checkbox slides in. The dot IS the selection affordance.
End-to-end: “Select” enters mode → dots morph to rings → tap rows to select → toolbar appears → choose action → feedback → “Done” exits.
“Move” opens a glass picker above the toolbar. Topics with emoji icons. Tap topic → memories move → rows animate out → toast “3 → Deployment” → selection clears.
Move to
Picker anchored to Move button, opens upward. Click outside or Esc closes. Same glass treatment as bar expand slot.
Same toolbar container. Actions collapse, destructive confirmation appears. Glassmorphism destructive treatment (kitchen 03). Forget LEFT, Keep RIGHT (cursor safety — cursor was on Forget, Keep appears where cursor is).
Before
After “Forget” tap
Long-press (500ms, haptic feedback) enters selection on mobile. “Select” text still available in header. Compact toolbar: icons only, same glass treatment. Fixed above safe area.
20 unassigned memories. Select by theme, batch move to topics. Primary batch use case.
Starting new project. Select all auth memories, copy as <memax-context> for agent.
After dreams merge duplicates. Forget stale memories the dream engine didn't catch.
Leaving project. Export topic memories as .md file for handoff.
Every batch action follows the same state machine. No dead states, no silent failures.
Normal rows. Category dots. No selection UI visible.
"Select" → "Done" in header. Dots morph to rings. Click toggles.
≥1 selected. Glass toolbar at bottom: count + Move/Copy/Export/Forget.
Toolbar morphs in place: "Forget N memories? [Forget] [Keep]".
Toolbar shows spinner + count. Actions disabled. No user interaction.
Selection exits. Bar toast: "3 memories forgotten" / "3 memories moved" / "Copied".
Selection stays. Bar toast (error): "Failed to forget/move". User can retry.
Pending state (toolbar morphs to spinner)
Success toast (bar notification)
Architecture (for LLM agents)
SelectionProvider— section-scoped (not layout-level). Each section wraps its own provider from the parent (KnowledgeGrid, TopicDetailPage). useSelection() inside the section reads the correct context. Esc exits globally.
BatchToolbar— portals to document.body via createPortal (escapes overflow-hidden + transform ancestors). z-60 (above bar z-50). Signals GlobalBar to hide via batch-active.ts bridge. Slides up (y 24→0, opacity, FAST spring). Uses bar tokens (--bar-bg, --bar-border).
batch-active.ts— module-level signal (same pattern as mutation-toast.ts). BatchToolbar signals active/inactive on mount/unmount. GlobalBar listens: bar slides down + fades (marginTop 0→24, avoids transform conflict with translateX(-50%)). Bar/toolbar share same bottom position — container morphing with animated swap.
MemoryRow— indicator slot morphs dot → ring → check (spring easing, no new DOM). Click: selectionActive ? toggle : navigate. Long-press 500ms on mobile enters selection.
FilterDropdown— portalled to body (escapes card overflow-hidden). Positioned via anchorRef getBoundingClientRect.
Toasts— via MutationCache meta (universal async action pattern, kitchen 28). No per-callsite toast wiring. Callsite callbacks for UI side-effects only (selection.exit()).
Server: POST /v1/memories/batch-delete (single SQL), POST /v1/memories/batch-move (topic or hub). Atomic — zero partial state.
4. Bar States
GlobalBar visual states — border, shadow, and animation combinations.
All states use 1px border. No border-width changes. Focus adds subtle ring + lift. Active states use notification banner, not border animation.
idle (rest)
focused (Cmd+K / tap)
typing
with notification
idle
recall-loading
recall-result
remember
All barNotification types with their icon, accent color, background, and auto-dismiss behavior. One notification at a time — new replaces old.
| Type | Icon | Scenario | Auto-dismiss | Actions |
|---|---|---|---|---|
| success | ✓ | Sent to memax, file imported | 4s | → view, ✕ dismiss |
| error | ✕ | Save failed, invalid file | 5s | ✕ dismiss |
| info | ✦ | Sending... (loading) | — | Replaced by success/error |
| dream | ✦ | Dream report findings | — | → report, ✕ dismiss |
| dreaming | ✦ | Dream in progress | Auto on complete | — |
| update | ✦ | App update available | — | → action, ✕ dismiss |
Live background previews per type:
Signature muted — Current — dream violet tint
Glass frost — Subtle neutral frosted glass
Dream aurora — Purple→blue→pink dream gradient — matches dream cards
Success (Sent to memax.) — ✓ Sent to memax. · title [Undo] — auto-dismiss 4s
Error (Save failed) — ✕ Save failed — auto-dismiss 5s
Info (Sending...) — ✦ Sending... — replaced by success/error
Unified: always 1px border (--bar-border at 12% light / oklch(0.30) dark). Focus adds ring + subtle lift. No border-width changes between states. Notification banner provides active state feedback, not the container border.
5. Loading Text
Time-progressive loading with animated star indicators. Each mode has a unique visual identity: sonar rings (recall), pulse dots (ask), settling glow (remember).
24. Bar Redesign
One bar, two operations. Push (remember) and Recall (search). Notification behind bar. Progressive results.
Default mode. User types, hits Enter (desktop) or send button (mobile). Textarea auto-expands for multi-line. Enter always newline on mobile.
remember, recall, or ask...
Empty — send disabled
Our auth uses JWT + API key dual path
Typing — send enabled
Meeting notes from product review: - Bar redesign approved - Progressive search confirmed - Mobile Enter = newline
Multi-line (auto-expand, Enter on mobile = newline)
Deploy process uses Fly.io with two toml files...
Sending — spinner
One bar, two operations. How does the user choose Push vs Recall? Three approaches compared — Option C (floating mode toggle) was chosen and implemented. Desktop uses subtle text, mobile uses pill chips. All share the same visual language (bar tint + button icon change).
Option A — Dual send buttons (no mode, direct action)
deployment process
Two buttons visible — user picks action
Enter = push, Cmd+Enter = recall. No mode state needed. But no keyword filter while typing (don't know which action user wants).
Option B — Logo toggle (click logo to switch mode)
meeting notes
Push mode (default) — logo normal
deployment process
Recall mode — logo tinted, click to toggle back
Logo is the toggle. Recall mode enables keyword filter while typing. Cmd+K also enters recall mode. Esc exits.
Option C — Floating mode toggle above bar ✓ Implemented
Desktop — subtle text toggle (12px, no fill)
Desktop push — “remember” bold, “recall” muted
Desktop recall — “recall” signature color, bar tinted 3%
Mobile — tappable pill chips (44px touch target, 13px font)
Mobile push — dark fill on active chip
Mobile recall — signature fill on active chip
Toggle floats above bar with AnimatePresence (slide-up 200ms enter, slide-down 200ms exit). Hidden when: notification visible, recall results expanding, or non-input phase. Smart detection auto-suggests mode but user can always override via toggle.
Results appear progressively: keyword (0ms) → server FTS (200ms) → semantic (1-3s) → AI synthesis (3-8s, Pro only). User sees results get better over time without waiting.
T=0ms — local keyword filter
auth architecture
Auth Middleware Design
JWT + API key dual path, agent_name from key...
2d
Security Rules
Owner isolation, boundary enforcement...
4d
T=1s — semantic recall ranked
auth architecture
Agent Identity Architecture
98%Auth = identity. API key → agent_name, OAuth JWT → claim...
Authentication · claude-code · 2d
MCP OAuth JWT Fix
91%Added AgentName claim to JWT, MCP OAuth tokens carry identity...
Authentication · memax-team · claude-code · 3d
Security Rules
74%Owner isolation at data layer, every query filters by owner_id...
Security · 1w
T=3s — AI synthesis (Pro)
auth architecture
Agent Identity Architecture
98%Auth = identity. API key → agent_name, OAuth JWT → claim...
Authentication · claude-code · 2d
MCP OAuth JWT Fix
91%Added AgentName claim to JWT, MCP OAuth tokens carry identity...
Authentication · memax-team · claude-code · 3d
Complete — AI answer + sources
auth architecture
Agent Identity Architecture
98%Auth = identity. API key → agent_name, OAuth JWT → claim...
Authentication · claude-code · 2d
MCP OAuth JWT Fix
91%Added AgentName claim to JWT...
Authentication · memax-team · claude-code · 3d
Free — keyword + semantic only
deployment
Deployment Process
95%Fly.io two-process deploy...
CI/CD Pipeline
78%GitHub Actions workflow...
✦ AI answers available on
Pro — auto AI synthesis
deployment
Deployment Process
95%Fly.io two-process deploy...
Push empty
Push ready
Sending
Recall empty
Recall ready
Push: dark fill (↑ ArrowUp). Recall: signature fill (✦ Sparkles). Loading: spinner replaces icon. Disabled: ghost, /40 opacity.
Mobile Enter always inserts newline — send button is the only way to submit. Textarea auto-expands via field-sizing: content (scrollbar hidden). Mode toggle uses tappable pill chips. Camera/file icon visible when input is empty.
remember, recall, or ask...
Mobile — 1 line + camera icon
Line one of my note Line two continues Line three gets long
Mobile — multi-line (auto-expand, scrollbar hidden)
what do I know about deploy
Mobile — recall mode with pill chips
Industry standard (ChatGPT, Claude, iMessage): auto-expand + scroll within bar. No fullscreen compose mode — keeps context visible. Scrollbar hidden via scrollbarWidth: none.
Files are staged above the input row as chips. Multiple files batch into one push. Images from clipboard auto-stage. Drag-drop onto bar. URL paste auto-detects and offers capture.
Auth flow diagram
Single image pasted — chip above input
Design review materials
Batch — 3 files dragged, text optional
remember, recall, or ask...
URL pasted — auto-detect, offer capture
Drag-drop — bar highlights as drop zone
Drop files here...
| Input | Desktop | Mobile |
|---|---|---|
| Image paste | Cmd+V → auto-stage as chip | Long-press paste → auto-stage |
| File drag-drop | Drag onto bar → dashed border → drop → stage | N/A (no drag on mobile) |
| URL paste | Paste URL → chip + “Capture page” button | Same |
| Batch files | Multi-select drag or Cmd+V multiple → all staged as chips | Photo picker → multi-select → stage |
| Text + files | Optional — text becomes memory title/note, files are attachments | Same |
| Mobile photo | N/A | 📷 icon next to send button → camera/gallery picker |
Notification is a separate fixed element (z-49) behind the bar (z-50). Slides up from behind. Own border-radius, own background. Zero seam line — completely independent DOM elements.
Success — 4s undo window, then auto-dismiss. Undo restores content to input + deletes memory.
Error — auto-dismiss 5s
Recalling — dismiss cancels
Dream report — persistent
Dream in progress — auto-dismiss on complete
Notification transitions in-place behind bar. Each state replaces the previous — no stacking, no flicker.
T=0ms — user hits Enter. Input clears. Notification slides up behind bar.
T=instant — API accepted. Notification replaces in-place. Auto-dismiss 4s.
Error — notification replaces in-place. Content restored to input.
| State | Notification | Bar input | Transition |
|---|---|---|---|
| Idle | None | Placeholder | → Remembering (on Enter) |
| Sending | ✦ Sending... (info, optimistic) | Cleared | → Sent (success) or → Error |
| Sent | ✓ Sent to memax. → title [Undo] (success, 4s window) | Ready for new input | → Idle (auto-dismiss) or → Undone (undo clicked) |
| Undone | Dismissed — content restored to input | Content restored | → Idle (instant) |
| Error | ✕ Save failed (error, 5s auto-dismiss) | Content restored | → Idle (dismiss) — user can retry |
Recall has a longer lifecycle. Notification shows during initial search, then dismisses when results take over. AI synthesis is inline in results — no separate notification.
| State | Notification | Bar expand | Bar input | Transition |
|---|---|---|---|---|
| Idle | None | Empty | Query text | → Searching (on Enter) |
| Searching | ✦ Searching... (recalling type, × cancels) | Keyword results (instant) | Shows query (editable) | → Results (semantic ready) |
| Results | Dismissed — results speak for themselves | Ranked results | Query preserved | → Synthesizing (Pro, auto) |
| Synthesizing(Pro) | None | Results + ✦ streaming below | Query preserved | → Complete (stream done) |
| Complete | None | Results + AI answer + [copy] | Query preserved | → Idle (Esc layered: clear query → dismiss results) |
| Error | ✕ Search failed (error, 5s auto-dismiss) | Empty | Query preserved | → Idle (dismiss) — user can retry |
Key rule: notification only during the “Searching” phase. Once results appear, notification dismisses — results are the feedback. AI synthesis uses inline ✦ cursor, not a notification.
Searching — notification active, keyword results appearing
auth architecture
Auth Middleware Design
JWT + API key dual path...
Results ready — notification gone, results are the feedback
auth architecture
Agent Identity Architecture
98%Auth = identity. API key → agent_name...
claude-code
MCP OAuth JWT Fix
91%Added AgentName claim to JWT...
claude-code
Security Rules
74%Owner isolation at data layer...
| Action | Desktop | Mobile |
|---|---|---|
| Send (push/recall) | Enter | Send button tap |
| Newline | Auto-expand (field-sizing: content) | Enter (always newline) |
| Switch to Recall | Click “recall” toggle / smart detection | Tap “✦ recall” chip |
| Back to Push | Click “remember” toggle | Tap “↑ remember” chip |
| Dismiss results | Esc (layered) | Tap outside / X |
| Focus bar | Cmd+K / click | Tap bar |
| Expand (multiline) | Auto (field-sizing) | Auto + expand icon at 3 lines |
| Voice (future) | Mic icon in send area | Mic icon in send area |
Mode toggles float above bar. Desktop: subtle text toggle (“remember · recall”). Mobile: pill chips with icons. Smart detection from detectIntent() auto-suggests mode. User can always override by tapping toggle.
1. Default mode is “remember” (push) — active text is bold fg-1
2. Click “recall” → logo tints signature, button becomes ✦, bg gets 3% tint
3. Smart detection: question words (what/how/why/是否/什么...) or auto-suggest recall mode
4. Toggle stays visible during input phase — user may want to override
5. Toggle fades out (slide-down, 200ms) when recall keyword results expand
6. Toggle fades back in (slide-up, 200ms) when results clear
7. Toggle hidden when notification is showing (mutually exclusive position)
Desktop — subtle text toggle
Mobile — tappable pill chips (44px touch)
6. Tree Panel
Knowledge tree navigation. Three responsive modes: desktop pinned (content shifts), desktop overlay (floats on top), mobile master/detail (Notion-style push). Reuses TopicTreeNode, TopicTreeContent patterns.
Full-height sticky sidebar. Logo stays fixed (same position always). Tree header aligns with CONTENT_TOP. ChevronsLeft collapses. Content push animated via motion.div width 0↔280. Hover left edge reveals overlay. Persisted.
Content shifts right
Sidebar takes space in layout flow
No toggle button. Hover left edge (12px zone, 150ms delay) reveals overlay. Starts at HEADER_TOP (not full height). » pins to layout flow. Mouse-leave dismisses after 300ms. Backdrop at 8% opacity.
Content stays full width
Hover left edge to reveal
ListTree icon next to “Your Knowledge” page header (right side). Opens bottom sheet overlay with drag handle. Toggle is contextual, not in fixed header.
Your Knowledge
10 memories · 5 topics
Tree IS the page content. Tap a topic → detail slides in from right. Back arrow returns. No separate toggle button. No overlay. This is the target pattern for mobile knowledge navigation.
Your Knowledge
147 memories · 6 topics
Topic detail
Memory list here
Collapsed (has children)
Active (selected)
Leaf (no children)
Drop target (DnD hover)
Waiting for first dream
Topics appear after your first dream
memax organizes your memories during the nightly dream cycle.
Dreams disabled
Dreams are turned off
Enable dreams to organize your knowledge.
Production files: topic-tree-panel.tsx (provider + pinned/peek), topic-tree-content.tsx (data + empty states), topic-tree-node.tsx (recursive node). layout.tsx (SidebarSlot, DesktopTreeHoverEdge, BrandLogo).
Desktop: pinned (sticky, ChevronsLeft) → collapsed (invisible) → hover left 12px zone (150ms) → overlay (top:HEADER_TOP, ChevronsRight to pin) → pinned. Content push: motion.div width 0↔280. isPinned in localStorage.
Mobile: ListTree icon in page header (topic-grid.tsx) opens bottom sheet. No fixed toggle button. Future target: master/detail push (22d).
7. Hub Experience
Team hub: ambient workspace switching via avatar dropdown (top-right), memory attribution, management, invite. Personal by default, team by intent.
Integrated into existing avatar dropdown (top-right). Single-hub users see no hub section. Multi-hub: hub list appears between user info and theme/lang toggles. Active hub shown with checkmark. Users icon for team hubs. “Create team hub” link at bottom. Current hub name shown in user subtitle.
Single hub user (no hub section)
Derek
52 memories
Multi-hub user (hub switcher in dropdown)
Derek
backend-team · 31 memories
Team memories show lucide Users + hub name (text-[12px] text-fg-4). Personal memories show no badge (default = no label). Only visible when user has 2+ hubs. Processing row unchanged. Flat dividers, icon containers match production.
OAuth2 refresh flow with token rotation strategy...
Push toast: “Sent — memax will organize.” (personal) or “Sent → hub-name — memax will organize.” (team). Recall results show hub attribution in cross-hub mode. Bar clears immediately after submit (chat-style, no blocking confirmation).
Push to personal hub (toast, no hub label)
Push to team hub (toast with target)
Recall results (cross-hub, shows attribution)
Inline form in settings dialog Account tab, below existing hub list. Name input → auto-generated slug → Create. Hub appears in pill dropdown immediately. Also accessible via dropdown “Create team hub” link.
Workspace
Create team hub
Your team will share a knowledge base. Invite members after creation.
Click team hub in settings → expands to show members + invite link. Flat row dividers. Role badges. Copy invite link. Owner/admin only. Same Surface pattern as memory detail.
5 members · 31 memories · created 2w ago
Invite link
https://memax.app/invite/a8f3c2e9...Expires Apr 12, 2026
Centered card, same visual language as login. Shows hub name, memory count, member count, inviter. Single CTA. Expired tokens show error state.
Join backend-team
31 shared memories
5 members
Invited by Derek
Search and contribute to
your team's shared knowledge.
Expires Apr 12, 2026
Inbox is hub-scoped. Shows unassigned memories for active hub. Clean state uses DREAM_PURPLE ✦. All states match production patterns (Clock/Inbox icons, flat dividers, icon containers).
Team inbox (has items)
Will be organized in the next dream cycle
Will be organized in the next dream cycle
Inbox clean
Your memory is clean
All states for hub creation: loading (breathing ✦), error (inline message), success (auto-switch + open management). Maps to teams-section.tsx HubCreateForm.
Creating
Error
Hub name already taken
Success → management
When hub has 1 member (owner) and 0 memories, replace member list with prominent invite CTA. This is the first thing an owner sees after creating a hub.
1 member · 0 memories · just now
Invite your first teammate
Share a link to start building shared knowledge.
Four states: not generated (CTA button), generating (breathing ✦), generated (code + copy + expiry), error (inline message). Maps to hub-management.tsx InviteLinkSection.
Not generated
Generating
Generated
https://memax.app/invite/a8f3c2e9...Expires Apr 14, 2026
Error
Could not generate invite link.
Logged in: single CTA. Not logged in: “Sign in to join” → OAuth → return to page → auto-accept. Expired/used show error. Accepted shows redirect.
Logged in
Join backend-team
5 members · Invited by Derek
Not logged in
Join backend-team
5 members · Invited by Derek
→ OAuth → returnTo in localStorage → auto-accept on return
Expired
Invite expired
Ask the hub owner for a new link.
Accepted
Joined!
Redirecting to your team's memory hub...
Two-step: button → destructive confirmation with memory count + member impact. “Personal memories are not affected.” After delete: return to list, auto-switch to personal.
Default
Confirmation
Permanently delete backend-team and all 31 memories. 4 members will lose access.
Personal memories are not affected.
Owner/Admin: full management (members + invite + delete). Contributor/Viewer: read-only member list + leave only. Roles affect ACTIONS not VIEWS — all roles see the same memories.
Owner / Admin
Contributor / Viewer
No invite button. No delete. Read-only member list + leave only.
Hub switcher = scope filter. Selection controls BOTH what you see (read) AND where you push (write). “All” is read-only aggregation — push defaults to personal. Single-hub users see no filter UI.
Hub scope selector (in dropdown)
Derek
All · 95 memories
Scope model
Recall always crosses all hubs.
Regardless of view scope, recall searches everything accessible. Hub badges on results show origin.
Knowledge page title adapts to active scope. Personal = “Your Knowledge”, Team = “{name}'s Knowledge”, All = “All Knowledge”. Breadcrumb root also adapts.
Scope: All
All Knowledge
95 memories · 8 topics
Scope: Personal
Your Knowledge
52 memories · 5 topics
Scope: Team
backend-team's Knowledge
31 memories · 3 topics
Bar shows where push goes. Personal scope: no indicator (default). Team scope: hub name pill right of input. All scope: “→ Personal” default, user can tap to change. Push toast always confirms target.
Bar — team scope (push target shown)
Bar — personal scope (no indicator needed)
Bar — “All” scope (push defaults to personal)
Push toasts by scope
Push target rules:
• Personal scope → push to personal hub (no indicator)
• Team scope → push to that team (arrow pill indicator)
• All scope → push to personal (muted indicator, tappable to switch)
• Single-hub users → no scope UI, no push indicator
Uses existing bar notification system. Success type, 2s auto-dismiss. Avatar subtitle updates immediately. No new component.
After switching hub in dropdown, bar shows confirmation notification. Avatar subtitle updates immediately.
Success type, 2s auto-dismiss. Uses existing bar notification system. No new component needed.
In settings Account tab. Click name → inline edit → save on blur/Enter. display_name is the single source of truth (seeded from OAuth, editable here). Used in attribution rows, settings panel, avatar dropdown.
yejiahaoderek@gmail.com
Inline edit: no modal, no save button. Bottom border appears on hover, solidifies on focus. Save on blur or Enter. PATCH /v1/auth/me with display_name. Optimistic update in useAuth.
Industry pattern: Slack, Notion, Linear, Vercel, GitHub, Figma all use top-left scope selector. 2-click standard. Full context swap on switch.
Routing: switched hub sets read context only. Push writes require explicit targeting (for example X-Hub-ID) or fall back to personal. Recall reads across all accessible hubs.
New user invite flow: Click link → see hub info → “Sign in to join” → OAuth with returnTo → auto-accept on return → switch to hub → land in /home.
23. Agent Management
Unified agent identity, API keys, config sync, activity stats. Settings > Agents tab northstar.
Each connected agent gets a unified card. Identity icon with accent color, status dot (active/idle/inactive), stats summary. Expand for API keys, config files, rename, revoke.
API Keys
Synced Configs· 30 extracted
User can customize the display name shown in memory attribution and across the app. The agent slug (claude-code) is immutable — set during memax setup. Priority: user custom → AGENT_NAMES map → raw slug.
Display mode
claude-code
Editing mode
claude-code
Display name is user-customizable. Slug (claude-code) is immutable, auto-set from agent type during memax setup. Display name flows to memory attribution.
Three levels of removal: revoke single key, revoke all keys for an agent, or full disconnect (keys + configs). Memories are always preserved — they belong to the user, not the agent.
Normal state
Revoke confirmation
Disconnect agent (revoke all keys + delete configs)
Two-stage confirmation pattern. Revoking a single key is granular. “Disconnect agent” revokes all keys + deletes all configs for that agent. Memories pushed by the agent are always preserved (they belong to the user, not the agent).
Status derived from last_usedon API keys. No MCP polling needed — every API call updates the timestamp.
Each agent type gets a unique icon + accent color. Consistent across attribution, settings, and management surfaces. Icon is mapped from agent_name slug, not user-configurable (keeps visual consistency).
claude-code
claude-ai
cursor
codex
gemini
copilot
windsurf
openclaw
opencode
generic
memax
Loading
No agents
Connect your first agent
Claude Code, Cursor, Windsurf, Copilot, Codex, Gemini
Data model
Agent identity is derived from api_keys.agent_name — set during memax setup. Each agent type can have multiple keys (per-hub, per-device). Activity status from api_keys.last_used.
Display name priority: user custom → AGENT_NAMES map → raw slug. Stored as agent_display_name on api_keys (future migration). Unified constant shared across memory-row, settings, and attribution.
New endpoint needed: GET /v1/agents/summary — aggregates api_keys + configs + memory counts per agent_name.
Production files: agent-configs-section.tsx, settings-dialog.tsx, memory-row.tsx. API: GET /v1/auth/api-keys, GET /v1/configs.
10. Dream Experience
Dream reports, action cards, and review flows. Background intelligence made visible. Premium visual treatment with dreamy purple/blue/pink palette.
Dreams haven't run yet
They run nightly to organize your memory
Scanning 247 memories
Your memory is clean
Processing timed out. Your memories are safe.
Dreams are turned off
92% similarity
kept: Server components render on the server and send HTML to the client.
merged: RSC renders server-side, sending HTML.
Last accessed 90 days ago. No recent recalls matched.
Two notes disagree about the default cache TTL.
87% similarity · both mention Redis cache configuration
Note A
“Default cache TTL is 3600 seconds (1 hour)”
Note B
“Set cache TTL to 1800s for optimal performance”
Cards stacked visually. Resolve top card → it flies away, next card springs forward. Stack depth shows remaining count. Interactive demo below.
A
“Default cache TTL is 3600 seconds (1 hour)”
B
“Set cache TTL to 1800s for optimal performance”
i18n keys: dreams.noRuns, dreams.running, dreams.completed,
dreams.clean, dreams.failed, dreams.disabled, dreams.merge,
dreams.archive, dreams.contradiction, dreams.review.*
Dreams are passive — surface results gently, no forced interruptions. Contradictions need human judgment (amber = attention, not urgency). Purple/blue/pink = dream visual language. Card stack: Robinhood-style stacked resolution — resolve top card, next appears.
29. Topic Redesign
Three-tier topic cards, 'what's new' signal, recall→topic bridge, borderless detail. MECE: Browse, Find, Review, Organize, Discover, Export.
One component, three tiers based on memory count. Large spans 2 cols on desktop. "N new" uses typographic weight + Dream Violet left accent. Progress bar shows relative size.
Blue-green deploys, rollback procedures, staging automation, Fly.io machine health checks
JWT tokens, OAuth, API key rotation, boundary enforcement
Error handling, context propagation, interface design
React Server Components, Tailwind, Radix primitives
PostgreSQL, pgvector, migrations, connection pooling
Large (20+): 2-col span, progress bar, subtopic chips, full description
Medium (5-19): standard card, name + desc + count
Small (1-4): horizontal scroll pills, minimal
"N new": text-fg-2 font-medium + shadow lift (no border accent)
No new content
Error handling, context propagation
3 new since last visit
JWT tokens, OAuth, API key rotation
Left: clean, no accent. Right: "3 new" at text-fg-2 font-medium + shadow lift (no left border). Client-side localStorage per-topic timestamps.
Recall results grouped by topic. Topics with >1 match get a clickable header row. ✦ prefix on dream-discovered topics.
Topic rows: icon + name + match count + → chevron. ✦ prefix for dream-discovered. Individual results indented (px-6). Uses topic_id + topic_name from RecalledMemory.
JWT tokens, OAuth flows, API key management, boundary enforcement, and security audit results.
Borderless on --background. Breadcrumb → icon + name (editable) → description → meta
Time boundary: centered divider with last visit date. New memories above, old below.
Copy button: "Copy N for AI" — exports all topic memories for context.
Blue-green deploys, rollback procedures
JWT tokens, OAuth
Error handling, context
Mobile: single column. Large = full card. Medium = compact. Small = horizontal scroll pills. Tree trigger as discoverable row at top.
Existing topic
Dream-created topic
✦ prefix on dream-created topics (created_at after last dream run). Disappears after first visit. Dream Violet signals "AI found this."
Three tiers. Large (20+ or has children) spans 2 cols. Medium (5-19) standard card. Small (1-4) horizontal scroll pills. One component with tier prop.
"N new" via typography + shadow. text-fg-2 font-medium + shadow lift (no left border accents). No badges, no dots. Client-side localStorage per-topic timestamps.
Recall → topic bridge. Group results by topic_id. Clickable header row with match count. ✦ prefix on dream-discovered topics.
Borderless detail.Topic detail matches memory detail — content on --background. Time boundary for "since your last visit."
Mobile is structural. Single column grid. Small topics = horizontal pills. Tree trigger as discoverable row.
Progress bar = relative size. Largest topic = 100%. Others proportional. bg-surface-2 track, bg-surface-3 fill. No color.
21. Explorations
Proposed / Not Yet Implemented
Proposed / Not Yet Implemented
Option A
#e8956a warm peach
Option B
#d4845e terra
Option C
#c9916e sand
Current (emerald)
#10b981 emerald
22. Coverage Gaps
State coverage matrix — what's implemented, partial, or missing across every surface.
| Location | Loading | Empty | Error | Processing |
|---|---|---|---|---|
| Bar recall | ✅ | ✅ | ❌ | — |
| Bar remember | ✅ | — | ❌ | — |
| Bar AI synthesis | ✅ | — | ⚠️ | — |
| Memory grid | ⚠️ | ✅ | ❌ | ✅ |
| Note detail | ✅ | ⚠️ | ❌ | ✅ |
| Settings | ❌ | ❌ | ❌ | — |
| Brain view | ✅ | ✅ | ❌ | — |
✅ implemented · ⚠️ partial · ❌ missing · — N/A
Priority: error states across bar + memory grid + settings
memax design system · Foundations → Components → Patterns · docs/design/memax-design-system.md