feat: US-001 - Install @xenova/transformers and add generate-embeddings script skeleton
This commit is contained in:
+126
-111
@@ -1,185 +1,200 @@
|
||||
{
|
||||
"project": "Portfolio — Login Logo & Blur Refinements",
|
||||
"branchName": "ralph/login-logo-refinements",
|
||||
"description": "Refine the login screen's CVMIS logo animation, backdrop blur coverage/intensity, and align visual details (border radius, shadows, colors, typography) with the dashboard design system.",
|
||||
"project": "Portfolio — Semantic Search & AI Chat",
|
||||
"branchName": "ralph/semantic-search",
|
||||
"description": "Replace Fuse.js command palette search with client-side semantic vector search (ONNX model), then add a Gemini Flash-powered AI chat widget.",
|
||||
"userStories": [
|
||||
{
|
||||
"id": "US-001",
|
||||
"title": "Skip to login phase for dev iteration",
|
||||
"description": "As a developer, I want to skip boot/ECG and land directly on the login screen so I can iterate on login changes quickly.",
|
||||
"title": "Install @xenova/transformers and add generate-embeddings script skeleton",
|
||||
"description": "As a developer, I need the Transformers.js dependency installed and a runnable script scaffold so subsequent stories can generate and use embeddings.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/App.tsx, change the initial Phase state from 'boot' to 'login'",
|
||||
"The boot, ECG, and login phases remain in code — only the initial state changes",
|
||||
"App loads directly to the login screen on refresh",
|
||||
"npm install @xenova/transformers",
|
||||
"Create scripts/generate-embeddings.ts with a main() function that imports the pipeline from @xenova/transformers",
|
||||
"Script loads the all-MiniLM-L6-v2 model and embeds a single test string, logging the vector length to confirm it works",
|
||||
"Add npm script: \"generate-embeddings\": \"npx tsx scripts/generate-embeddings.ts\"",
|
||||
"Running npm run generate-embeddings prints the vector length (384) and exits cleanly",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 1,
|
||||
"passes": true,
|
||||
"notes": "Temporary — final story reverts this. Phase state is on line 47 of App.tsx."
|
||||
"notes": "Use @xenova/transformers (not @huggingface/transformers — the Xenova fork has better Node.js ONNX support). The model ID is 'Xenova/all-MiniLM-L6-v2'. Pipeline type is 'feature-extraction'. tsx is already available via npx for running TypeScript scripts."
|
||||
},
|
||||
{
|
||||
"id": "US-002",
|
||||
"title": "Extract animation timing into named constants",
|
||||
"description": "As a developer, I want all animation timing values in CvmisLogo.tsx exposed as named constants at the top of the file so I can quickly tune rise speed, fan speed, fan delay, and easing.",
|
||||
"title": "Build rich text representations for each palette item",
|
||||
"description": "As a developer, I want each palette item to have a natural-language paragraph for embedding that captures deep context, not just the title.",
|
||||
"acceptanceCriteria": [
|
||||
"Named constants at the top of CvmisLogo.tsx for: rise duration (currently 500ms), fan delay after rise (currently 500ms), fan duration (currently 600ms), fan easing curve, fan rotation angle (currently ±50°), fan horizontal spacing (currently ±16px), right pill stagger delay (currently 30ms)",
|
||||
"Additional named constants for overlap blend: OVERLAY_BLEND_START_PROGRESS (target 0.5), OVERLAP_BLEND_MAX_OPACITY (target 0.2), OVERLAP_BLEND_TRANSITION_DURATION",
|
||||
"Component behaviour unchanged when constants retain current values",
|
||||
"Constants are clearly named and grouped with a brief comment block",
|
||||
"New function buildEmbeddingTexts() in src/lib/search.ts that returns Array<{ id: string, text: string }> for all palette items",
|
||||
"Consultation items include: role, org, duration, history narrative, examination bullets, coded entry descriptions",
|
||||
"Skill items include: name, category, frequency, proficiency percentage, years of experience",
|
||||
"KPI items include: value, label, explanation, story context and outcomes",
|
||||
"Investigation items include: name, methodology, tech stack list, results",
|
||||
"Education items include: title, institution, type, research detail",
|
||||
"Quick Action items include: title and subtitle (short text is fine)",
|
||||
"Achievement items include: title, subtitle, and linked KPI story context if available",
|
||||
"Each text is a readable natural-language paragraph, not a keyword dump",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 2,
|
||||
"passes": true,
|
||||
"notes": "Read CvmisLogo.tsx carefully first — some timing values are inline in useEffect/motion props. Extract them ALL to top-level constants. The blend constants are new (for US-004) but should be defined now with sensible defaults."
|
||||
"passes": false,
|
||||
"notes": "This function will be used by both the build script (to generate embeddings) and potentially by the chat widget (for context). Import the raw data files (consultations, skills, kpis, investigations, documents) to access the full data beyond what buildPaletteData() surfaces. The id must match the PaletteItem id so embeddings can be correlated."
|
||||
},
|
||||
{
|
||||
"id": "US-003",
|
||||
"title": "Scale logo and branding block to ~50% of login card height",
|
||||
"description": "As a visitor, I want the CVMIS logo and branding text to be larger and more prominent, occupying roughly half the login card's height.",
|
||||
"title": "Generate and commit embeddings.json",
|
||||
"description": "As a developer, I want the generate-embeddings script to produce a complete embeddings.json file using the rich text representations.",
|
||||
"acceptanceCriteria": [
|
||||
"Logo cssHeight scaled up from current clamp(48px, 4vw, 64px) — target approximately clamp(160px, 18vw, 280px), tune visually for balance",
|
||||
"Width scales proportionally (SVG viewBox preserves aspect ratio)",
|
||||
"The branding block (logo + CVMIS title + subtitle + spacing) occupies approximately 50% of the total login card height",
|
||||
"Logo does not overflow or clip on mobile viewports (>=375px wide)",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
"scripts/generate-embeddings.ts imports buildEmbeddingTexts() from src/lib/search.ts",
|
||||
"Script embeds each item's text using the all-MiniLM-L6-v2 model via @xenova/transformers pipeline",
|
||||
"Outputs src/data/embeddings.json as an array of { id: string, embedding: number[] }",
|
||||
"Each embedding is a 384-dimensional float array",
|
||||
"Running npm run generate-embeddings regenerates the file successfully",
|
||||
"The JSON file is valid and parseable",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 3,
|
||||
"passes": true,
|
||||
"notes": "CvmisLogo accepts cssHeight prop (string) for CSS clamp values. The branding block is in LoginScreen.tsx — the logo, title, and subtitle are in a flex column container. Adjust the cssHeight prop on the CvmisLogo component and check the ratio visually."
|
||||
"passes": false,
|
||||
"notes": "The pipeline returns a Tensor — use .tolist() or .data to extract the raw float array. Mean-pool across the token dimension (dim 1) to get a single 384-d vector per input. Process items sequentially to avoid OOM in Node. The output file will be ~200KB for ~40 items with 384 floats each."
|
||||
},
|
||||
{
|
||||
"id": "US-004",
|
||||
"title": "Increase branding text to match dashboard typography scale",
|
||||
"description": "As a visitor, I want the CVMIS title and subtitle on the login screen to be larger and more in line with the dashboard's typography scale.",
|
||||
"title": "Preload ONNX model during boot sequence",
|
||||
"description": "As a visitor, I want the semantic search model to download in the background during the boot/ECG/login phases so it's ready when I reach the dashboard.",
|
||||
"acceptanceCriteria": [
|
||||
"CVMIS title font size increased from 13px — target approximately 18-20px to match dashboard heading scale",
|
||||
"CV Management Information System subtitle font size increased from 11px — target approximately 13-14px",
|
||||
"Both remain in font-ui (Elvaro Grotesque) with appropriate weight hierarchy",
|
||||
"Text remains visually balanced with the larger logo above and the login form below",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
"New src/lib/embedding-model.ts module that exports: initModel(), embedQuery(text: string), and isModelReady()",
|
||||
"initModel() loads the all-MiniLM-L6-v2 pipeline from @xenova/transformers and stores it in a module-level variable",
|
||||
"embedQuery() returns a Promise<number[]> (384-d vector) for a given text string",
|
||||
"isModelReady() returns boolean indicating if the model has finished loading",
|
||||
"initModel() is called in App.tsx useEffect on mount (during boot phase) — fire and forget, no await",
|
||||
"If initModel() fails (network error, etc.), isModelReady() remains false — no error thrown or shown",
|
||||
"Model is cached by @xenova/transformers in IndexedDB — subsequent page loads are near-instant",
|
||||
"Boot/ECG/login animations are not affected by model loading",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 4,
|
||||
"passes": true,
|
||||
"notes": "The title and subtitle are in LoginScreen.tsx in the branding section. Look for the CVMIS text and its fontSize style. Use clamp() for responsive sizing consistent with the card's responsive approach."
|
||||
"passes": false,
|
||||
"notes": "Use pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2') which auto-downloads and caches the ONNX model. The module-level pattern (let pipelineInstance = null) avoids React re-render issues. embedQuery should mean-pool the tensor output the same way as the build script. Wrap initModel() in a try/catch that silently swallows errors."
|
||||
},
|
||||
{
|
||||
"id": "US-005",
|
||||
"title": "Add overlap blend effect on fanning capsules",
|
||||
"description": "As a visitor, I want to see a subtle color blend where the fanning capsules overlap, matching the multiply-blend effect from the Remotion animation.",
|
||||
"title": "Implement cosine similarity search module",
|
||||
"description": "As a developer, I need a semantic search function that compares a query embedding against pre-computed item embeddings and returns ranked results.",
|
||||
"acceptanceCriteria": [
|
||||
"CSS mix-blend-mode: multiply applied to the fanning pill elements in CvmisLogo.tsx",
|
||||
"Blend effect is not visible at the start of the fan animation",
|
||||
"Blend fades in starting at ~50% of fan animation progress (using OVERLAY_BLEND_START_PROGRESS constant from US-002)",
|
||||
"Blend reaches max intensity by end of fan (using OVERLAP_BLEND_MAX_OPACITY constant from US-002)",
|
||||
"Max blend opacity approximately 0.2 (20%)",
|
||||
"Blend is only perceptible where capsules actually overlap on light backgrounds",
|
||||
"Blend transition feels smooth, not abrupt",
|
||||
"Respects prefers-reduced-motion (no animation, show final state)",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
"New src/lib/semantic-search.ts module",
|
||||
"Exports semanticSearch(queryEmbedding: number[], embeddings: Array<{ id: string, embedding: number[] }>, threshold?: number): Array<{ id: string, score: number }>",
|
||||
"Uses cosine similarity: dot(a,b) / (magnitude(a) * magnitude(b))",
|
||||
"Results sorted by score descending",
|
||||
"Optional threshold parameter filters out low-relevance results (default 0.3)",
|
||||
"Exports loadEmbeddings() that imports embeddings.json and returns the parsed array",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 5,
|
||||
"passes": true,
|
||||
"notes": "Use framer-motion's useTransform or a progress-based approach to derive blend opacity from fan animation progress. The pill elements are <g> groups inside the SVG. Apply mixBlendMode: 'multiply' as a style and animate the group's opacity using the timing constants from US-002. The blend should only be visible during/after the fan phase, not during the rise phase."
|
||||
"passes": false,
|
||||
"notes": "Keep the cosine similarity implementation simple — no libraries needed for 384-d vectors over ~40 items. The loadEmbeddings function can use a dynamic import or direct import of the JSON file (Vite handles JSON imports natively)."
|
||||
},
|
||||
{
|
||||
"id": "US-006",
|
||||
"title": "Extend backdrop blur to cover full dashboard including TopBar",
|
||||
"description": "As a visitor, I want the frosted-glass blur behind the login card to cover the entire dashboard including the TopBar, so nothing behind the overlay is sharp.",
|
||||
"title": "Integrate semantic search into command palette",
|
||||
"description": "As a visitor, I want the command palette to use semantic search when available, falling back to Fuse.js otherwise.",
|
||||
"acceptanceCriteria": [
|
||||
"Blur overlay z-index raised above TopBar z-index (TopBar is zIndex: 100, overlay is currently z-50). Overlay must be >= zIndex: 110 or similar",
|
||||
"TopBar, Sidebar, and all dashboard content are uniformly blurred behind the overlay",
|
||||
"Login card itself remains crisp and unblurred (card z-index above overlay)",
|
||||
"Blur still fades out during the dissolve/exit transition",
|
||||
"CommandPalette.tsx checks isModelReady() from embedding-model.ts",
|
||||
"When model is ready and query is non-empty: call embedQuery(query), then semanticSearch() against loaded embeddings, then map result IDs back to PaletteItem objects",
|
||||
"When model is NOT ready: use existing Fuse.js search (current behavior preserved exactly)",
|
||||
"Search is debounced by ~200ms to avoid calling embedQuery on every keystroke",
|
||||
"Results maintain existing groupBySection() grouping and section ordering",
|
||||
"Existing keyboard navigation, action routing, and UI unchanged",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
"Verify in browser: search 'data analysis' surfaces analytics-related roles/skills not just items with 'data' in title"
|
||||
],
|
||||
"priority": 6,
|
||||
"passes": true,
|
||||
"notes": "LoginScreen outer overlay currently has 'fixed inset-0 z-50'. TopBar is zIndex: 100. The overlay needs z-index > 100 to cover it. The login card inside the overlay doesn't need its own z-index since it's a child of the overlay. Check that the dissolve exit animation (isExiting) still works after the z-index change."
|
||||
"passes": false,
|
||||
"notes": "The debounce is important — embedQuery takes ~20-50ms per call. Use a useRef + setTimeout pattern or a simple debounce hook. The mapping from semantic search results (id + score) back to PaletteItems should use a Map for O(1) lookup. Keep the Fuse.js imports and buildSearchIndex — they're the fallback path."
|
||||
},
|
||||
{
|
||||
"id": "US-007",
|
||||
"title": "Reduce backdrop blur intensity by ~50%",
|
||||
"description": "As a visitor, I want the backdrop blur to be softer so the dashboard behind is slightly more visible while still providing contrast for the login card.",
|
||||
"title": "Chat widget — floating button component",
|
||||
"description": "As a visitor, I see a floating chat button at the bottom-right of the dashboard that I can click to open a chat panel.",
|
||||
"acceptanceCriteria": [
|
||||
"Blur value reduced from blur(20px) to approximately blur(10px)",
|
||||
"The blur value is a named constant co-located with other LoginScreen timing constants for easy adjustment",
|
||||
"Login card remains clearly readable against the softened backdrop",
|
||||
"The dissolve exit animation still animates blur from 10px to 0px",
|
||||
"New src/components/ChatWidget.tsx component",
|
||||
"Renders a 48px circular button, fixed position, bottom: 24px, right: 24px",
|
||||
"Uses teal accent background (var(--accent)), white MessageCircle icon from lucide-react",
|
||||
"Shadow: var(--shadow-md). Hover: var(--shadow-lg) + scale(1.05) transition",
|
||||
"Button has a subtle entrance animation: fade + translateY(8px) → translateY(0), delayed ~1s after mount",
|
||||
"Respects prefers-reduced-motion (no animation, just visible)",
|
||||
"z-index above dashboard content but below command palette overlay (z-index 90)",
|
||||
"onClick toggles an isOpen state (panel rendering comes in next story)",
|
||||
"Mounted in DashboardLayout.tsx",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 7,
|
||||
"passes": true,
|
||||
"notes": "The blur is in two places in LoginScreen.tsx: the initial style (backdropFilter: blur(20px)) and the exit animation (animates from blur(20px) to blur(0px)). Extract the blur value to a constant like BACKDROP_BLUR_PX = 10, then reference it in both places."
|
||||
"passes": false,
|
||||
"notes": "Use framer-motion for the entrance animation to match the rest of the app's motion patterns. The button should use font-ui for any text. On mobile (<640px), button is 40px and positioned bottom: 16px, right: 16px. The VITE_GEMINI_API_KEY env var check can wait until the Gemini integration story — for now just render the button unconditionally."
|
||||
},
|
||||
{
|
||||
"id": "US-008",
|
||||
"title": "Align login card border radius and shadow with dashboard design system",
|
||||
"description": "As a visitor, I want the login card to feel like it belongs to the same design system as the dashboard by matching border radius and shadow tokens.",
|
||||
"title": "Chat widget — panel UI with message display",
|
||||
"description": "As a visitor, I want a chat panel that opens above the floating button where I can type questions and see responses.",
|
||||
"acceptanceCriteria": [
|
||||
"Login card border radius changed from 12px to 8px (matching var(--radius-card) / dashboard cards)",
|
||||
"Login input fields and button border radius changed from 4px to 6px (matching var(--radius-sm) / dashboard inner elements)",
|
||||
"Login card shadow upgraded from shadow-sm to shadow-lg (0 8px 32px rgba(26,43,42,0.12)) — appropriate for a floating modal over blurred backdrop",
|
||||
"Use CSS custom property references (var(--radius-card), var(--radius-sm)) where available rather than hardcoded values",
|
||||
"Chat panel renders when isOpen is true, positioned above the floating button (bottom: 88px, right: 24px)",
|
||||
"Panel dimensions: 380px wide, max-height 480px, with overflow-y auto for messages",
|
||||
"Header: title text ('Ask about Andy'), close button (X icon)",
|
||||
"Message area: user messages right-aligned in teal-tinted bubbles, assistant messages left-aligned in light gray bubbles",
|
||||
"Input area at bottom: text field with placeholder 'Ask me anything...', send button (Send icon)",
|
||||
"Enter key submits message, Shift+Enter for newline",
|
||||
"Panel entrance animation: scale(0.95) + opacity(0) → scale(1) + opacity(1), 200ms ease-out",
|
||||
"Panel exit animation: reverse of entrance",
|
||||
"Respects prefers-reduced-motion",
|
||||
"Responsive: on mobile (<640px), panel is full-width (left: 0, right: 0, bottom: 0) with rounded top corners only",
|
||||
"Messages are stored in component state as Array<{ role: 'user' | 'assistant', content: string }>",
|
||||
"Submitting a message adds it to state and shows it in the UI (no API call yet — assistant response is a placeholder)",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 8,
|
||||
"passes": true,
|
||||
"notes": "Check index.css for whether --radius-card and --radius-sm exist as CSS custom properties. If not, use the hardcoded values (8px and 6px) directly. The card shadow is currently set via inline style — update to the shadow-lg value. The login card borderRadius is in the card's inline style object."
|
||||
"passes": false,
|
||||
"notes": "Use the design system tokens: var(--surface) for panel bg, var(--border-light) for borders, var(--text-primary) for text, var(--accent) for user bubble bg at 10% opacity, font-ui for body text, font-geist for timestamps. The placeholder assistant response can be a static string like 'AI chat coming soon — this is a preview of the chat interface.' This lets us verify the full UI before wiring up Gemini."
|
||||
},
|
||||
{
|
||||
"id": "US-009",
|
||||
"title": "Replace hardcoded colors with design tokens",
|
||||
"description": "As a developer, I want the login screen to reference the same CSS custom properties as the dashboard so palette changes propagate consistently.",
|
||||
"title": "Chat widget — Gemini Flash integration",
|
||||
"description": "As a visitor, I can ask natural language questions and get intelligent, streamed answers about Andy's experience.",
|
||||
"acceptanceCriteria": [
|
||||
"Input text color changed from hardcoded #111827 to var(--text-primary, #1A2B2A)",
|
||||
"Cursor/caret color changed from hardcoded #0D6E6E to var(--accent, #0D6E6E)",
|
||||
"Button background colors changed from hardcoded #0D6E6E / #0A8080 / #085858 to var(--accent) / var(--accent-hover) / appropriate pressed variant using token references",
|
||||
"Any other hardcoded color values in LoginScreen.tsx that have corresponding CSS custom properties use the token instead",
|
||||
"No visual change (token values resolve to same colors currently)",
|
||||
"New src/lib/gemini.ts module that exports sendChatMessage(messages: ChatMessage[], cvContext: string): AsyncGenerator<string>",
|
||||
"Calls Google Gemini Flash API (gemini-2.0-flash) using the REST API with fetch (no SDK needed)",
|
||||
"API key sourced from import.meta.env.VITE_GEMINI_API_KEY",
|
||||
"System prompt includes structured CV context built from buildEmbeddingTexts() output",
|
||||
"System prompt instructs the model to answer questions about Andy's professional experience accurately and concisely",
|
||||
"System prompt instructs the model to include relevant palette item IDs in its response as a JSON array at the end",
|
||||
"Responses are streamed using the Gemini streaming endpoint",
|
||||
"ChatWidget.tsx wires up real messages: on submit, calls sendChatMessage and streams tokens into the assistant message bubble",
|
||||
"Loading state shown (typing indicator) while waiting for first token",
|
||||
"If VITE_GEMINI_API_KEY is not set, chat button is still visible but panel shows 'Chat is currently unavailable' message",
|
||||
"If API call fails, show error message in chat: 'Sorry, I couldn't process that. Please try again.'",
|
||||
"Conversation history (last 10 messages) passed to API for multi-turn context",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 9,
|
||||
"passes": true,
|
||||
"notes": "Search LoginScreen.tsx for all hex color values (#xxxxxx) and check whether a corresponding CSS custom property exists in index.css. Some colors were already tokenized in the previous login rework (US-003 of previous run) — verify which ones are still hardcoded. The button has multiple color states (default, hover, pressed) — check all three."
|
||||
"passes": false,
|
||||
"notes": "Gemini REST streaming endpoint: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent?alt=sse&key=API_KEY. The response is SSE (server-sent events) — parse each 'data:' line as JSON and extract candidates[0].content.parts[0].text. The system prompt with CV context will be ~2-3K tokens — well within Gemini Flash limits. For the palette item IDs, instruct the model to end its response with a line like [ITEMS: id1, id2, id3] which can be parsed client-side."
|
||||
},
|
||||
{
|
||||
"id": "US-010",
|
||||
"title": "Fix minor typography inconsistencies",
|
||||
"description": "As a visitor, I want the login screen's typography weight and sizing to feel consistent with the dashboard's conventions.",
|
||||
"title": "Chat widget — clickable portfolio item cards in responses",
|
||||
"description": "As a visitor, I want AI chat responses to include clickable portfolio items so I can drill into relevant sections.",
|
||||
"acceptanceCriteria": [
|
||||
"Form label font weight increased from 500 to 600 (matching dashboard card header weight convention)",
|
||||
"Input text mid-value aligned to ~14px to match dashboard body text",
|
||||
"Button text mid-value aligned to ~15px",
|
||||
"Connection status indicator gap increased from 6px to 8px (matching dashboard CardHeader gap)",
|
||||
"No dramatic visual change — these are subtle alignment fixes",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 10,
|
||||
"passes": true,
|
||||
"notes": "These are small inline style tweaks in LoginScreen.tsx. The labels, inputs, and button already use clamp() for responsive sizing — just adjust the mid-values. The connection indicator gap is in the flex container styling near the bottom of the component."
|
||||
},
|
||||
{
|
||||
"id": "US-011",
|
||||
"title": "Re-enable boot sequence",
|
||||
"description": "As a user, I want the full boot → ECG → login → dashboard experience restored.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/App.tsx, change the initial Phase state back from 'login' to 'boot'",
|
||||
"Boot → ECG → Login → Dashboard sequence works end to end",
|
||||
"Login screen shows blurred dashboard behind it with reduced blur and full TopBar coverage",
|
||||
"Logo animation plays with blend effect, typing animation follows, connection indicator transitions, button pulses",
|
||||
"Clicking login dissolves the overlay to reveal the dashboard",
|
||||
"After parsing the assistant response, extract referenced palette item IDs from the [ITEMS: ...] suffix",
|
||||
"Render matched items as compact clickable cards below the answer text in the assistant bubble",
|
||||
"Cards reuse icon/color mapping from CommandPalette (iconByType, iconColorStyles)",
|
||||
"Cards show item title and subtitle in a compact horizontal layout",
|
||||
"Clicking a card triggers the same action routing as command palette via handlePaletteAction in DashboardLayout",
|
||||
"If no items are referenced or IDs don't match, no cards are shown (just the text answer)",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 11,
|
||||
"passes": true,
|
||||
"notes": "Simple revert of US-001. Phase state is on line 47 of App.tsx."
|
||||
"priority": 10,
|
||||
"passes": false,
|
||||
"notes": "The action routing needs to flow from ChatWidget up to DashboardLayout. Add an onAction prop to ChatWidget (same pattern as CommandPalette). DashboardLayout passes handlePaletteAction to ChatWidget. Export iconByType and iconColorStyles from CommandPalette (or extract to a shared module) so ChatWidget can reuse them."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user