From 1d1df52f3b8c4ab180b78f3e9b439f5d11de2bd4 Mon Sep 17 00:00:00 2001 From: Tom Huang <1043269994@qq.com> Date: Fri, 8 May 2026 17:38:29 +0800 Subject: [PATCH] feat(skills/live-artifact): add 7 example dashboards + contract demo (#716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(skills/live-artifact): add 7 example dashboards + contract demo Seven self-contained HTML prototypes under skills/live-artifact/examples/, each with a distinct visual identity and built-in interactivity for video demos: stock-dashboard.html - Bloomberg-style trading floor (dark) crypto-dashboard.html - DeFi/web3 cyber terminal with on-chain ribbon crm-table-live.html - multi-dim CRM with Grid/Kanban/Gallery/Calendar view switcher (light productivity) monday-operator-live.html - editorial Monday-morning briefing (paper) competitor-radar-live.html - mission-control radar with rotating sweep and RGB threat tiers baby-health-live.html - soft pastel parental panel stock-portfolio-live/ - full live-artifact contract example: 102 escaped html_template_v1 bindings + 7 data-od-repeat blocks, ready to register via 'tools live-artifacts create' Each interactive HTML carries refresh-with-flash, view switching, AI panel regeneration, clickable rows/cards that mutate state, and toast notifications. Self-contained - only Google Fonts as external dep. stock-portfolio-live/ demonstrates the daemon contract: template.html + data.json + artifact.json + provenance.json. Refresh runners can rewrite data.json without re-authoring the template. * fix(skills/live-artifact): address PR #716 review feedback - Unroll data-od-repeat blocks into indexed data.* bindings so renderHtmlTemplateV1 can interpolate them (it does not expand data-od-repeat or repeat-local aliases like {{t.label}}). - Rename catalysts[].body to catalysts[].text to satisfy the bounded JSON validator's forbidden-key list (body is rejected case-insensitively); update template binding accordingly. Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code) * fix(skills/live-artifact): make stock-portfolio provenance.json contract-compliant - generatedBy: free-form string -> "agent" (LiveArtifactProvenanceGenerator enum) - sources[].kind -> sources[].type with LiveArtifactProvenanceSourceType enum values (connector for brokerage/quotes connectors, derived for AI recommendation) - Drop non-contract per-source `note` and top-level `summary`/`transformations`/ `refreshContract`/`safetyNotes` fields; preserve their content under the contract-allowed `notes` field so the example survives schema validation. Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code) * fix(skills/live-artifact): use strict ISO-8601 generatedAt in provenance The daemon's `validateIsoDate` requires `Date.toISOString()` round-trip equality, so timezone-offset notation like `2026-05-06T14:32:18-05:00` fails validation even though it parses. Switch to the canonical UTC form `2026-05-06T19:32:18.000Z` (same instant), which the validator accepts. * feat(skills): surface examples/*.html as derived skill cards + Live filter A skill that ships hand-crafted samples under examples/*.html (e.g. live-artifact's stock dashboard, baby health monitor) now lights up one gallery card per file instead of a single parent card whose preview can only ever show one of them. The parent stays in the listing tagged aggregatesExamples=true so findSkillById and Use this prompt still resolve back to its SKILL.md body, but the Examples tab hides it so the derived : cards aren't shadowed by a duplicate preview. Subfolder layouts (examples//template.html + data.json) are deliberately skipped — their templates still hold {{data.x}} placeholders that only the daemon-side renderer fills in, so showing the raw template would render visible braces in the gallery. Ship the baked output as examples/.html alongside the folder to surface it. Adds an examples.modeLive filter pill (translated across all 21 locales) that selects skill.scenario === 'live', so refreshable / connector-backed samples are easy to find without scrolling through every desktop prototype. live-artifact's SKILL.md gains scenario: live so it (and every derived card) lights up there. Co-authored-by: Cursor * perf(web): parallelize entry-view bootstrap so each tab renders independently Bootstrap used to wait on a single Promise.all behind a global 'Loading workspace…' placeholder, which made the slowest endpoint (typically /api/agents on cold start, since it probes CLI versions) gate every tab including the ones that don't need agents at all. Splits the global bootstrapping flag into per-resource loading flags (agentsLoading, skillsLoading, dsLoading, projectsLoading, promptTemplatesLoading) plus a daemonConfigLoaded flag for the merged daemon config. Each tab now blocks only on the data it actually needs: Examples renders as soon as skills land, Design Systems on dsList, Designs on projects+skills+designSystems, etc. Auto-selecting the first available agent and the default design system moves into dedicated effects gated on daemonConfigLoaded so they no longer race ahead of the daemon-stored choice and overwrite it with a freshly picked first-available pick. EntryView swaps its single loading prop for skillsLoading, designSystemsLoading, projectsLoading, promptTemplatesLoading so each inner tab can pick the right gate without leaking the parent's coarse state. Co-authored-by: Cursor --------- Co-authored-by: Cursor --- apps/daemon/src/server.ts | 83 +- apps/daemon/src/skills.ts | 168 +- apps/web/src/App.tsx | 224 +- apps/web/src/components/EntryView.tsx | 117 +- apps/web/src/components/ExamplesTab.tsx | 30 +- apps/web/src/i18n/locales/ar.ts | 1 + apps/web/src/i18n/locales/de.ts | 1 + apps/web/src/i18n/locales/en.ts | 1 + apps/web/src/i18n/locales/es-ES.ts | 1 + apps/web/src/i18n/locales/fa.ts | 1 + apps/web/src/i18n/locales/fr.ts | 1 + apps/web/src/i18n/locales/hu.ts | 1 + apps/web/src/i18n/locales/id.ts | 1 + apps/web/src/i18n/locales/ja.ts | 1 + apps/web/src/i18n/locales/ko.ts | 1 + apps/web/src/i18n/locales/pl.ts | 1 + apps/web/src/i18n/locales/pt-BR.ts | 1 + apps/web/src/i18n/locales/ru.ts | 1 + apps/web/src/i18n/locales/tr.ts | 1 + apps/web/src/i18n/locales/uk.ts | 1 + apps/web/src/i18n/locales/zh-CN.ts | 1 + apps/web/src/i18n/locales/zh-TW.ts | 1 + apps/web/src/i18n/types.ts | 1 + packages/contracts/src/api/registry.ts | 8 + skills/live-artifact/SKILL.md | 1 + .../examples/baby-health-live.html | 761 ++++++ .../examples/competitor-radar-live.html | 1006 +++++++ .../examples/crm-table-live.html | 1174 +++++++++ .../examples/crypto-dashboard.html | 2316 +++++++++++++++++ .../examples/monday-operator-live.html | 919 +++++++ .../examples/stock-dashboard.html | 2246 ++++++++++++++++ .../stock-portfolio-live/artifact.json | 16 + .../examples/stock-portfolio-live/data.json | 453 ++++ .../stock-portfolio-live/provenance.json | 19 + .../stock-portfolio-live/template.html | 1071 ++++++++ 35 files changed, 10488 insertions(+), 142 deletions(-) create mode 100644 skills/live-artifact/examples/baby-health-live.html create mode 100644 skills/live-artifact/examples/competitor-radar-live.html create mode 100644 skills/live-artifact/examples/crm-table-live.html create mode 100644 skills/live-artifact/examples/crypto-dashboard.html create mode 100644 skills/live-artifact/examples/monday-operator-live.html create mode 100644 skills/live-artifact/examples/stock-dashboard.html create mode 100644 skills/live-artifact/examples/stock-portfolio-live/artifact.json create mode 100644 skills/live-artifact/examples/stock-portfolio-live/data.json create mode 100644 skills/live-artifact/examples/stock-portfolio-live/provenance.json create mode 100644 skills/live-artifact/examples/stock-portfolio-live/template.html diff --git a/apps/daemon/src/server.ts b/apps/daemon/src/server.ts index 17bccd364..5d472ab42 100644 --- a/apps/daemon/src/server.ts +++ b/apps/daemon/src/server.ts @@ -30,7 +30,7 @@ import { spawnEnvForAgent, } from './agents.js'; import { migrateLegacyDataDirSync } from './legacy-data-migrator.js'; -import { findSkillById, listSkills } from './skills.js'; +import { findSkillById, listSkills, splitDerivedSkillId } from './skills.js'; import { validateLinkedDirs } from './linked-dirs.js'; import { buildWindowsFolderDialogCommand, parseFolderDialogStdout } from './native-folder-dialog.js'; import { listCodexPets, readCodexPetSpritesheet } from './codex-pets.js'; @@ -2663,18 +2663,56 @@ export async function startServer({ port = 7456, host = process.env.OD_BIND_HOST // so we resolve the actual directory via listSkills() rather than guessing. // // Resolution order: - // 1. /example.html — fully-baked static example (preferred) - // 2. /assets/template.html + + // 1. Derived id (`:`): + // /examples/.html — pre-baked single-file sample. + // Subfolder layouts (e.g. live-artifact's + // `examples//template.html`) are intentionally not served: + // they still contain `{{data.x}}` placeholders that only the + // daemon-side renderer fills in, and serving the raw template + // would render visible placeholder braces in the gallery. + // 2. /example.html — fully-baked static example (preferred) + // 3. /assets/template.html + // /assets/example-slides.html — assemble at request time // by replacing the `` marker with the snippet // and patching the placeholder . Lets a skill ship one // canonical seed plus a small content fragment, so the example // never drifts from the seed. - // 3. <skillDir>/assets/template.html — raw template, no content slides - // 4. <skillDir>/assets/index.html — generic fallback + // 4. <skillDir>/assets/template.html — raw template, no content slides + // 5. <skillDir>/assets/index.html — generic fallback + // 6. First .html in <skillDir>/examples/ — used as a friendly fallback + // so a skill that aggregates examples (like live-artifact) still has + // a real preview on its parent card instead of returning 404. app.get('/api/skills/:id/example', async (req, res) => { try { const skills = await listSkills(SKILLS_DIR); + + // 1. Derived `<parent>:<child>` id — resolve straight to the matching + // file under <parentDir>/examples/. Done before findSkillById so the + // parent's normal fallback chain never accidentally serves a stale + // file when a sample is missing (we'd rather 404 explicitly). + const derived = splitDerivedSkillId(req.params.id); + if (derived) { + const parent = findSkillById(skills, derived.parentId); + if (!parent) { + return res.status(404).type('text/plain').send('skill not found'); + } + const candidate = path.join( + parent.dir, + 'examples', + `${derived.childKey}.html`, + ); + if (fs.existsSync(candidate)) { + const html = await fs.promises.readFile(candidate, 'utf8'); + return res + .type('text/html') + .send(rewriteSkillAssetUrls(html, parent.id)); + } + return res + .status(404) + .type('text/plain') + .send('derived example not found'); + } + const skill = findSkillById(skills, req.params.id); if (!skill) { return res.status(404).type('text/plain').send('skill not found'); @@ -2715,11 +2753,44 @@ export async function startServer({ port = 7456, host = process.env.OD_BIND_HOST .type('text/html') .send(rewriteSkillAssetUrls(html, skill.id)); } + + // Friendly fallback for skills that aggregate examples in a sibling + // `examples/` folder (e.g. live-artifact). The parent card would + // otherwise 404 even though plenty of perfectly valid samples ship + // alongside SKILL.md; pick the first .html file alphabetically so + // direct URL access (e.g. deep links) shows something representative. + // Subfolder layouts are excluded for the same reason as the derived + // resolver above — their `template.html` still has unresolved + // `{{data.x}}` placeholders. + const examplesDir = path.join(skill.dir, 'examples'); + if (fs.existsSync(examplesDir)) { + let entries: string[] = []; + try { + entries = await fs.promises.readdir(examplesDir); + } catch { + entries = []; + } + entries.sort(); + for (const name of entries) { + if (name.startsWith('.')) continue; + if (!name.toLowerCase().endsWith('.html')) continue; + const direct = path.join(examplesDir, name); + try { + const html = await fs.promises.readFile(direct, 'utf8'); + return res + .type('text/html') + .send(rewriteSkillAssetUrls(html, skill.id)); + } catch { + continue; + } + } + } + res .status(404) .type('text/plain') .send( - 'no example.html, assets/template.html, or assets/index.html for this skill', + 'no example.html, assets/template.html, assets/index.html, or examples/*.html for this skill', ); } catch (err) { res.status(500).type('text/plain').send(String(err)); diff --git a/apps/daemon/src/skills.ts b/apps/daemon/src/skills.ts index bd23f56b9..d1599c1d1 100644 --- a/apps/daemon/src/skills.ts +++ b/apps/daemon/src/skills.ts @@ -58,26 +58,45 @@ export async function listSkills(skillsRoot) { const hasAttachments = await dirHasAttachments(dir); const mode = data.od?.mode || inferMode(body, data.description); const surface = normalizeSurface(data.od?.surface, mode); + const platform = normalizePlatform( + data.od?.platform, + mode, + body, + data.description + ); + const scenario = normalizeScenario( + data.od?.scenario, + body, + data.description + ); + const designSystemRequired = data.od?.design_system?.requires ?? true; + const upstream = + typeof data.od?.upstream === "string" ? data.od.upstream : null; + const previewType = data.od?.preview?.type || "html"; + const parentId = data.name || entry.name; + const parentBody = hasAttachments ? withSkillRootPreamble(body, dir) : body; + // Pre-compute derived examples so the parent entry can advertise + // `aggregatesExamples` in the same push. The frontend uses that + // flag to hide the parent card from the gallery (its preview would + // duplicate one of the derived cards), while the daemon keeps the + // parent in the listing so `findSkillById` still resolves it for + // system-prompt composition and id alias lookups. + const derivedExamples = await collectDerivedExamples(dir); + const aggregatesExamples = derivedExamples.length > 0; out.push({ - id: data.name || entry.name, - name: data.name || entry.name, + id: parentId, + name: parentId, description: data.description || "", triggers: Array.isArray(data.triggers) ? data.triggers : [], mode, surface, craftRequires: normalizeCraftRequires(data.od?.craft?.requires), - platform: normalizePlatform( - data.od?.platform, - mode, - body, - data.description - ), - scenario: normalizeScenario(data.od?.scenario, body, data.description), - previewType: data.od?.preview?.type || "html", - designSystemRequired: data.od?.design_system?.requires ?? true, + platform, + scenario, + previewType, + designSystemRequired, defaultFor: normalizeDefaultFor(data.od?.default_for), - upstream: - typeof data.od?.upstream === "string" ? data.od.upstream : null, + upstream, featured: normalizeFeatured(data.od?.featured), // Optional metadata hints used by 'Use this prompt' fast-create so // the resulting project mirrors the shipped example.html. Each hint @@ -87,9 +106,49 @@ export async function listSkills(skillsRoot) { speakerNotes: normalizeBoolHint(data.od?.speaker_notes), animations: normalizeBoolHint(data.od?.animations), examplePrompt: derivePrompt(data), - body: hasAttachments ? withSkillRootPreamble(body, dir) : body, + aggregatesExamples, + body: parentBody, dir, }); + + // Surface every example sitting next to a SKILL.md as its own card so + // a single skill (e.g. live-artifact) can ship a small gallery of + // hand-crafted samples without needing one SKILL.md per sample. Each + // derived card inherits the parent's mode/platform/surface/scenario + // so existing TYPE/SURFACE filters keep working; the synthetic id + // `<parent>:<child>` lets `/api/skills/:id/example` resolve straight + // to the matching HTML on disk. We deliberately do not inherit + // `featured` so derived cards never crowd the magazine row. + for (const example of derivedExamples) { + out.push({ + id: `${parentId}:${example.key}`, + name: humanizeExampleName(example.key), + description: data.description || "", + triggers: Array.isArray(data.triggers) ? data.triggers : [], + mode, + surface, + craftRequires: [], + platform, + scenario, + previewType, + designSystemRequired, + defaultFor: [], + upstream, + featured: null, + fidelity: normalizeFidelity(data.od?.fidelity), + speakerNotes: normalizeBoolHint(data.od?.speaker_notes), + animations: normalizeBoolHint(data.od?.animations), + examplePrompt: derivePrompt(data), + aggregatesExamples: false, + // Inherit the parent's full SKILL.md body so 'Use this prompt' + // on a derived card seeds the agent with the same workflow the + // parent describes. Without this, picking a derived card would + // compose an empty system prompt and the agent would have no + // skill instructions. + body: parentBody, + dir, + }); + } } catch { // Skip unreadable entries — this is discovery, not validation. } @@ -97,6 +156,87 @@ export async function listSkills(skillsRoot) { return out; } +// Discover example artifacts that live alongside SKILL.md under +// `<dir>/examples/`. Only the single-file layout is surfaced: +// +// `examples/<name>.html` — pre-baked, self-contained sample. +// +// We deliberately do not surface the subfolder layout (e.g. live-artifact's +// `examples/<name>/template.html` + `data.json`) because those templates +// still hold `{{data.x}}` placeholders that only the daemon-side renderer +// fills in. Showing the raw template would render visible placeholder +// braces in the gallery — worse than not surfacing the example at all. +// To ship a subfolder-style example, place the baked output beside the +// folder as `examples/<name>.html` (the canonical render) and keep the +// subfolder around as agent-readable source. +async function collectDerivedExamples(dir) { + const examplesDir = path.join(dir, "examples"); + let entries = []; + try { + entries = await readdir(examplesDir, { withFileTypes: true }); + } catch { + return []; + } + const out = []; + for (const entry of entries) { + if (!entry.isFile()) continue; + if (!entry.name.toLowerCase().endsWith(".html")) continue; + const key = entry.name.replace(/\.html$/i, ""); + if (!isSafeExampleKey(key)) continue; + out.push({ key }); + } + // Stable order so the gallery renders the same sequence on every reload. + out.sort((a, b) => a.key.localeCompare(b.key)); + return out; +} + +// Reject keys that could escape the examples folder or break the +// `<parent>:<child>` id format. Letters/digits/dash/dot/underscore only, +// and never the dotfile path-traversal patterns. +function isSafeExampleKey(key) { + if (!key || key.startsWith(".")) return false; + if (key.includes(":")) return false; + return /^[A-Za-z0-9._-]+$/.test(key); +} + +// Turn a basename like `stock-portfolio-live` into a title-cased label +// (`Stock Portfolio Live`) so the gallery card has a readable heading +// without forcing every example to ship its own frontmatter. +function humanizeExampleName(key) { + return key + .replace(/[-_]+/g, " ") + .replace(/\s+/g, " ") + .trim() + .split(" ") + .map((word) => + word.length === 0 + ? word + : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() + ) + .join(" "); +} + +// Used by `/api/skills/:id/example` to resolve a derived id back to its +// on-disk file. Returns null when the key is unsafe; the route checks +// `fs.existsSync` against the returned path before reading. +export function resolveDerivedExamplePath(parentDir, childKey) { + if (!isSafeExampleKey(childKey)) return null; + return path.join(parentDir, "examples", `${childKey}.html`); +} + +// Split a `<parent>:<child>` synthetic id into its two halves. Returns +// null for non-derived ids so the caller can fall through to the regular +// listing-based lookup. +export function splitDerivedSkillId(id) { + if (typeof id !== "string") return null; + const idx = id.indexOf(":"); + if (idx <= 0 || idx === id.length - 1) return null; + const parentId = id.slice(0, idx); + const childKey = id.slice(idx + 1); + if (!isSafeExampleKey(childKey)) return null; + return { parentId, childKey }; +} + // Skills that ship side files (e.g. `assets/template.html`, `references/*.md`) // need the agent to know where the skill lives on disk — relative paths in the // SKILL.md body would otherwise resolve against the agent's CWD, which is the diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 611838dae..990b89274 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -127,10 +127,21 @@ export function App() { const [appVersionInfo, setAppVersionInfo] = useState<AppVersionInfo | null>( null, ); - // Goes false once the bootstrap effect has finished its initial round of - // fetches. The entry view uses this to show shimmer / skeleton states - // instead of an "empty" page that flickers before data lands. - const [bootstrapping, setBootstrapping] = useState(true); + // Per-resource loading flags. Each goes false the moment its own fetch + // resolves so each entry-view tab can render as its data lands instead of + // every tab waiting on the slowest endpoint (typically `/api/agents`, + // which probes CLI versions and can take seconds on cold start). The entry + // view picks the right flag for whichever tab the user is currently on. + const [agentsLoading, setAgentsLoading] = useState(true); + const [skillsLoading, setSkillsLoading] = useState(true); + const [dsLoading, setDsLoading] = useState(true); + const [projectsLoading, setProjectsLoading] = useState(true); + const [promptTemplatesLoading, setPromptTemplatesLoading] = useState(true); + // Goes true once the daemon-persisted config (agentId/designSystemId/etc.) + // has merged into local state. Auto-selection effects below wait on this + // so they don't race ahead of the daemon-stored choice and overwrite it + // with a freshly picked first-available agent. + const [daemonConfigLoaded, setDaemonConfigLoaded] = useState(false); // Narrower flag dedicated to the Composio API key hydration. The key is // persisted by the daemon (and only reflected back via apiKeyConfigured // + apiKeyTail), so after a dev-server restart there is a window where @@ -173,100 +184,158 @@ export function App() { }); }, [activeProjectId, activeFileName]); - // Bootstrap — detect daemon, load pickers, seed sensible defaults. + // Bootstrap — detect daemon, then fan out independent fetches so each + // entry-view tab can render the moment its own data lands. Earlier this + // was one Promise.all behind a global "Loading workspace…" placeholder, + // which made the slowest endpoint (typically `/api/agents` on cold start) + // gate every tab including the ones that don't need agents at all. useEffect(() => { let cancelled = false; (async () => { const alive = await daemonIsLive(); if (cancelled) return; setDaemonLive(alive); - const [ - agentList, - skillList, - dsList, - projectList, - templateList, - promptTemplateList, - versionInfo, - daemonConfig, - daemonComposioConfig, - ] = await Promise.all([ - alive ? fetchAgents() : Promise.resolve([] as AgentInfo[]), - alive ? fetchSkills() : Promise.resolve([] as SkillSummary[]), - alive - ? fetchDesignSystems() - : Promise.resolve([] as DesignSystemSummary[]), - alive ? listProjects() : Promise.resolve([] as Project[]), - alive ? listTemplates() : Promise.resolve([] as ProjectTemplate[]), - alive - ? fetchPromptTemplates() - : Promise.resolve([] as PromptTemplateSummary[]), - alive ? fetchAppVersionInfo() : Promise.resolve(null), - alive ? fetchDaemonConfig() : Promise.resolve(null), - alive ? fetchComposioConfigFromDaemon() : Promise.resolve(null), - ]); - if (cancelled) return; - setAgents(agentList); - setSkills(skillList); - setDesignSystems(dsList); - setProjects(projectList); - setTemplates(templateList); - setPromptTemplates(promptTemplateList); - setAppVersionInfo(versionInfo); - setConfig((prev) => { - // Merge daemon-persisted config — daemon values win for the fields - // it tracks so that the choice survives origin/storage resets. - const next = mergeDaemonConfig(prev, daemonConfig); + if (!alive) { + // No daemon — clear every loading flag so empty states render + // instead of the entry view sitting on indefinite spinners. + setAgentsLoading(false); + setSkillsLoading(false); + setDsLoading(false); + setProjectsLoading(false); + setPromptTemplatesLoading(false); + setDaemonConfigLoaded(true); + // Composio hydration also depends on the daemon. With no daemon + // we just keep whatever localStorage already held; drop the + // skeleton so the Settings → Connectors input reflects state. + setComposioConfigLoading(false); + return; + } - if (alive) { + void fetchAgents().then((list) => { + if (cancelled) return; + setAgents(list); + setAgentsLoading(false); + }); + + void fetchSkills().then((list) => { + if (cancelled) return; + setSkills(list); + setSkillsLoading(false); + }); + + void fetchDesignSystems().then((list) => { + if (cancelled) return; + setDesignSystems(list); + setDsLoading(false); + }); + + void listProjects().then((list) => { + if (cancelled) return; + setProjects(list); + setProjectsLoading(false); + }); + + void listTemplates().then((list) => { + if (cancelled) return; + setTemplates(list); + }); + + void fetchPromptTemplates().then((list) => { + if (cancelled) return; + setPromptTemplates(list); + setPromptTemplatesLoading(false); + }); + + void fetchAppVersionInfo().then((info) => { + if (cancelled) return; + setAppVersionInfo(info); + }); + + // Daemon-persisted config + composio config land together so the + // welcome-modal decision and the daemon-side composio key both apply + // in one merge, avoiding a flash where local-only state is shown + // before daemon overrides it. + void Promise.all([ + fetchDaemonConfig(), + fetchComposioConfigFromDaemon(), + ]).then(([daemonConfig, daemonComposioConfig]) => { + if (cancelled) return; + setConfig((prev) => { + const next = mergeDaemonConfig(prev, daemonConfig); const hasLocalComposioKey = Boolean(next.composio?.apiKey?.trim()); if (!hasLocalComposioKey && daemonComposioConfig) { next.composio = daemonComposioConfig; } - if (!next.agentId) { - const firstAvailable = agentList.find((a) => a.available); - if (firstAvailable) next.agentId = firstAvailable.id; + saveConfig(next); + if (hasAnyConfiguredProvider(next.mediaProviders)) { + void syncMediaProvidersToDaemon(next.mediaProviders); } - if (!next.designSystemId && dsList.length > 0) { - next.designSystemId = - dsList.find((d) => d.id === 'default')?.id ?? dsList[0]!.id; - } - } - saveConfig(next); - if (alive && hasAnyConfiguredProvider(next.mediaProviders)) { - void syncMediaProvidersToDaemon(next.mediaProviders); - } - // Migrate localStorage prefs to daemon on first boot with the new - // endpoint. If daemon already had values the merge above used them; - // writing back is idempotent and ensures both sides stay in sync. - if (alive) { + // Migrate localStorage prefs to daemon on first boot with the new + // endpoint. If daemon already had values the merge above used + // them; writing back is idempotent and keeps both sides in sync. void syncConfigToDaemon(next); void syncComposioConfigToDaemon(next.composio); - } - // Pop the onboarding modal only on the first run. Once the user has - // saved or skipped past it once, we trust their stored config and - // let them re-open Settings explicitly via the env pill. - if (!next.onboardingCompleted) { - setSettingsWelcome(true); - setSettingsOpen(true); - } - return next; + // Pop the onboarding modal only on the first run. Once the user + // has saved or skipped past it once, we trust their stored config + // and let them re-open Settings explicitly via the env pill. + if (!next.onboardingCompleted) { + setSettingsWelcome(true); + setSettingsOpen(true); + } + return next; + }); + setDaemonConfigLoaded(true); + // Composio key hydration is part of this same daemon-config + // fetch — by the time we land here the daemon has either + // returned the saved-key shape (apiKeyConfigured + tail) or + // it errored and we kept whatever localStorage held. Either + // way it is safe to drop the skeleton. + setComposioConfigLoading(false); }); - setBootstrapping(false); - // Composio hydration is part of the same Promise.all above — by the - // time we land here either the daemon returned the saved-key shape - // (apiKeyConfigured + tail) or the daemon was offline and we kept - // whatever localStorage held. Either way it is safe to drop the - // skeleton: the input now reflects the source of truth. - setComposioConfigLoading(false); })(); return () => { cancelled = true; }; }, []); + // Auto-pick the first available agent once both the daemon-stored config + // and the agents listing have landed. Splitting this out of bootstrap + // avoids racing the local-config initial value against a slow agents + // probe — by the time this runs, daemonConfig has already overlaid the + // user's previous choice, so we only fill an empty slot. + useEffect(() => { + if (!daemonConfigLoaded || agentsLoading) return; + if (config.agentId) return; + const firstAvailable = agents.find((a) => a.available); + if (!firstAvailable) return; + setConfig((prev) => { + if (prev.agentId) return prev; + const next: AppConfig = { ...prev, agentId: firstAvailable.id }; + saveConfig(next); + void syncConfigToDaemon(next); + return next; + }); + }, [daemonConfigLoaded, agentsLoading, agents, config.agentId]); + + // Auto-pick the default design system the same way — only after daemon + // config has merged so we never overwrite a daemon-stored selection. + useEffect(() => { + if (!daemonConfigLoaded || dsLoading) return; + if (config.designSystemId) return; + if (designSystems.length === 0) return; + const id = + designSystems.find((d) => d.id === 'default')?.id ?? designSystems[0]!.id; + setConfig((prev) => { + if (prev.designSystemId) return prev; + const next: AppConfig = { ...prev, designSystemId: id }; + saveConfig(next); + void syncConfigToDaemon(next); + return next; + }); + }, [daemonConfigLoaded, dsLoading, designSystems, config.designSystemId]); + // One-shot self-healing migration for pets adopted before the // overlay learned atlas-row switching. If the stored pet is a // custom / codex pet whose imageUrl is a single-row strip @@ -658,7 +727,10 @@ export function App() { defaultDesignSystemId={config.designSystemId} config={config} agents={agents} - loading={bootstrapping} + skillsLoading={skillsLoading} + designSystemsLoading={dsLoading} + projectsLoading={projectsLoading} + promptTemplatesLoading={promptTemplatesLoading} onCreateProject={handleCreateProject} onImportClaudeDesign={handleImportClaudeDesign} onImportFolder={handleImportFolder} diff --git a/apps/web/src/components/EntryView.tsx b/apps/web/src/components/EntryView.tsx index e1bce0a61..afd934944 100644 --- a/apps/web/src/components/EntryView.tsx +++ b/apps/web/src/components/EntryView.tsx @@ -46,7 +46,15 @@ interface Props { defaultDesignSystemId: string | null; config: AppConfig; agents: AgentInfo[]; - loading?: boolean; + // Per-resource loading flags. Each tab gates its own content on whichever + // flag matches the data it renders, so a slow `/api/agents` probe does + // not block tabs that don't need agents. Templates are not gated here — + // the sidebar 'From template' tab renders an empty state until they + // arrive (fast fetch), which keeps the prop surface narrower. + skillsLoading?: boolean; + designSystemsLoading?: boolean; + projectsLoading?: boolean; + promptTemplatesLoading?: boolean; onCreateProject: (input: CreateInput & { pendingPrompt?: string }) => void; onImportClaudeDesign: (file: File) => Promise<void> | void; onImportFolder?: (baseDir: string) => Promise<void> | void; @@ -213,7 +221,10 @@ export function EntryView({ defaultDesignSystemId, config, agents, - loading = false, + skillsLoading = false, + designSystemsLoading = false, + projectsLoading = false, + promptTemplatesLoading = false, onCreateProject, onImportClaudeDesign, onImportFolder, @@ -469,7 +480,7 @@ export function EntryView({ connectors={connectors} connectorsLoading={connectorsLoading} onOpenConnectorsTab={() => onOpenSettings('composio')} - loading={loading} + loading={skillsLoading || designSystemsLoading} /> <div className="entry-side-foot"> <button @@ -565,47 +576,65 @@ export function EntryView({ </div> </div> <div className="entry-tab-content"> - {loading ? ( - <CenteredLoader label={t('entry.loadingWorkspace')} /> - ) : ( - <> - {topTab === 'designs' ? ( - <DesignsTab - projects={projects} - skills={skills} - designSystems={designSystems} - onOpen={onOpenProject} - onOpenLiveArtifact={onOpenLiveArtifact} - onDelete={onDeleteProject} - /> - ) : null} - {topTab === 'examples' ? ( - <ExamplesTab skills={skills} onUsePrompt={usePromptFromSkill} /> - ) : null} - {topTab === 'design-systems' ? ( - <DesignSystemsTab - systems={designSystems} - selectedId={defaultDesignSystemId} - onSelect={onChangeDefaultDesignSystem} - onPreview={previewDesignSystem} - /> - ) : null} - {topTab === 'image-templates' ? ( - <PromptTemplatesTab - surface="image" - templates={promptTemplates} - onPreview={setPreviewPromptTemplate} - /> - ) : null} - {topTab === 'video-templates' ? ( - <PromptTemplatesTab - surface="video" - templates={promptTemplates} - onPreview={setPreviewPromptTemplate} - /> - ) : null} - </> - )} + {topTab === 'designs' ? ( + // DesignsTab uses skills + designSystems for tag rendering on + // each card, so wait until projects + that metadata are present + // to avoid a flash of "No projects yet" before the real list + // arrives. + projectsLoading || skillsLoading || designSystemsLoading ? ( + <CenteredLoader label={t('common.loading')} /> + ) : ( + <DesignsTab + projects={projects} + skills={skills} + designSystems={designSystems} + onOpen={onOpenProject} + onOpenLiveArtifact={onOpenLiveArtifact} + onDelete={onDeleteProject} + /> + ) + ) : null} + {topTab === 'examples' ? ( + skillsLoading ? ( + <CenteredLoader label={t('common.loading')} /> + ) : ( + <ExamplesTab skills={skills} onUsePrompt={usePromptFromSkill} /> + ) + ) : null} + {topTab === 'design-systems' ? ( + designSystemsLoading ? ( + <CenteredLoader label={t('common.loading')} /> + ) : ( + <DesignSystemsTab + systems={designSystems} + selectedId={defaultDesignSystemId} + onSelect={onChangeDefaultDesignSystem} + onPreview={previewDesignSystem} + /> + ) + ) : null} + {topTab === 'image-templates' ? ( + promptTemplatesLoading ? ( + <CenteredLoader label={t('common.loading')} /> + ) : ( + <PromptTemplatesTab + surface="image" + templates={promptTemplates} + onPreview={setPreviewPromptTemplate} + /> + ) + ) : null} + {topTab === 'video-templates' ? ( + promptTemplatesLoading ? ( + <CenteredLoader label={t('common.loading')} /> + ) : ( + <PromptTemplatesTab + surface="video" + templates={promptTemplates} + onPreview={setPreviewPromptTemplate} + /> + ) + ) : null} </div> </main> {petRailHidden ? null : ( diff --git a/apps/web/src/components/ExamplesTab.tsx b/apps/web/src/components/ExamplesTab.tsx index 9a572da0b..52aa93055 100644 --- a/apps/web/src/components/ExamplesTab.tsx +++ b/apps/web/src/components/ExamplesTab.tsx @@ -19,7 +19,14 @@ interface Props { onUsePrompt: (skill: SkillSummary) => void; } -type ModeFilter = 'all' | 'prototype-desktop' | 'prototype-mobile' | 'deck' | 'document' | 'orbit'; +type ModeFilter = + | 'all' + | 'prototype-desktop' + | 'prototype-mobile' + | 'deck' + | 'document' + | 'orbit' + | 'live'; type SurfaceFilter = 'all' | Surface; type ScenarioFilter = string; @@ -38,6 +45,7 @@ const MODE_PILLS: { value: ModeFilter; labelKey: keyof Dict }[] = [ { value: 'deck', labelKey: 'examples.modeDeck' }, { value: 'document', labelKey: 'examples.modeDocument' }, { value: 'orbit', labelKey: 'examples.modeOrbit' }, + { value: 'live', labelKey: 'examples.modeLive' }, ]; const SCENARIO_LABEL_KEY: Record<string, keyof Dict> = { @@ -87,6 +95,12 @@ function matchesMode(skill: SkillSummary, filter: ModeFilter): boolean { return skill.mode === 'prototype' && skill.platform === 'mobile'; if (filter === 'document') return skill.mode === 'template'; if (filter === 'orbit') return skill.scenario === 'orbit'; + // Live artifacts ride on the prototype mode but want their own bucket so + // refreshable / connector-backed samples are easy to find without + // scrolling through every desktop prototype. The parent live-artifact + // skill and every derived `live-artifact:<example>` card share the + // `live` scenario, so they all light up here together. + if (filter === 'live') return skill.scenario === 'live'; return true; } @@ -104,8 +118,18 @@ function quotePrompt(locale: string, text: string): string { return locale === 'de' ? `„${text}“` : `“${text}”`; } -export function ExamplesTab({ skills, onUsePrompt }: Props) { +export function ExamplesTab({ skills: rawSkills, onUsePrompt }: Props) { const { locale, t } = useI18n(); + // Skills tagged `aggregatesExamples: true` are containers whose preview + // would just duplicate one of their derived `<parent>:<child>` cards + // (e.g. live-artifact ships a sample gallery under `examples/`). Drop + // them up front so every count, filter, and rendered card downstream + // sees only the user-facing entries. The full listing is still passed + // through for `findSkillById` lookups elsewhere in the app. + const skills = useMemo( + () => rawSkills.filter((s) => !s.aggregatesExamples), + [rawSkills], + ); // Hold preview HTML per skill across re-renders so cards never re-flicker. const [previews, setPreviews] = useState<Record<string, string | null>>({}); // Track per-skill fetch failures separately so the preview modal can show @@ -214,6 +238,7 @@ export function ExamplesTab({ skills, onUsePrompt }: Props) { deck: 0, document: 0, orbit: 0, + live: 0, }; for (const s of surfaceScoped) { if (matchesMode(s, 'prototype-desktop')) c['prototype-desktop']++; @@ -221,6 +246,7 @@ export function ExamplesTab({ skills, onUsePrompt }: Props) { if (matchesMode(s, 'deck')) c.deck++; if (matchesMode(s, 'document')) c.document++; if (matchesMode(s, 'orbit')) c.orbit++; + if (matchesMode(s, 'live')) c.live++; } return c; }, [skills, surfaceFilter]); diff --git a/apps/web/src/i18n/locales/ar.ts b/apps/web/src/i18n/locales/ar.ts index f6876ca1f..66a680a9e 100644 --- a/apps/web/src/i18n/locales/ar.ts +++ b/apps/web/src/i18n/locales/ar.ts @@ -407,6 +407,7 @@ export const ar: Dict = { 'examples.modeDeck': 'شرائح', 'examples.modeDocument': 'مستندات وقوالب', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'مباشر', 'examples.scenarioGeneral': 'عام', 'examples.scenarioEngineering': 'هندسة', 'examples.scenarioProduct': 'منتج', diff --git a/apps/web/src/i18n/locales/de.ts b/apps/web/src/i18n/locales/de.ts index d4d942576..b73e17ee5 100644 --- a/apps/web/src/i18n/locales/de.ts +++ b/apps/web/src/i18n/locales/de.ts @@ -300,6 +300,7 @@ export const de: Dict = { 'examples.modeDeck': 'Folien', 'examples.modeDocument': 'Dokumente & Templates', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'Live', 'examples.scenarioGeneral': 'Allgemein', 'examples.scenarioEngineering': 'Engineering', 'examples.scenarioProduct': 'Produkt', diff --git a/apps/web/src/i18n/locales/en.ts b/apps/web/src/i18n/locales/en.ts index 21e20a917..0d5d55a87 100644 --- a/apps/web/src/i18n/locales/en.ts +++ b/apps/web/src/i18n/locales/en.ts @@ -418,6 +418,7 @@ export const en: Dict = { 'examples.modeDeck': 'Slides', 'examples.modeDocument': 'Docs & templates', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'Live', 'examples.scenarioGeneral': 'General', 'examples.scenarioEngineering': 'Engineering', 'examples.scenarioProduct': 'Product', diff --git a/apps/web/src/i18n/locales/es-ES.ts b/apps/web/src/i18n/locales/es-ES.ts index 69a630f24..79f162f52 100644 --- a/apps/web/src/i18n/locales/es-ES.ts +++ b/apps/web/src/i18n/locales/es-ES.ts @@ -301,6 +301,7 @@ export const esES: Dict = { 'examples.modeDeck': 'Diapositivas', 'examples.modeDocument': 'Documentos y plantillas', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'En vivo', 'examples.scenarioGeneral': 'General', 'examples.scenarioEngineering': 'Ingeniería', 'examples.scenarioProduct': 'Producto', diff --git a/apps/web/src/i18n/locales/fa.ts b/apps/web/src/i18n/locales/fa.ts index e3f7ccd76..c746575fc 100644 --- a/apps/web/src/i18n/locales/fa.ts +++ b/apps/web/src/i18n/locales/fa.ts @@ -418,6 +418,7 @@ export const fa: Dict = { 'examples.modeDeck': 'اسلایدها', 'examples.modeDocument': 'اسناد و قالب‌ها', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'زنده', 'examples.scenarioGeneral': 'عمومی', 'examples.scenarioEngineering': 'مهندسی', 'examples.scenarioProduct': 'محصول', diff --git a/apps/web/src/i18n/locales/fr.ts b/apps/web/src/i18n/locales/fr.ts index f139958a6..f2db96ee7 100644 --- a/apps/web/src/i18n/locales/fr.ts +++ b/apps/web/src/i18n/locales/fr.ts @@ -407,6 +407,7 @@ export const fr: Dict = { 'examples.modeDeck': 'Diaporamas', 'examples.modeDocument': 'Docs et modèles', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'Live', 'examples.scenarioGeneral': 'Général', 'examples.scenarioEngineering': 'Ingénierie', 'examples.scenarioProduct': 'Produit', diff --git a/apps/web/src/i18n/locales/hu.ts b/apps/web/src/i18n/locales/hu.ts index 7ac48f6bd..bcc4ca290 100644 --- a/apps/web/src/i18n/locales/hu.ts +++ b/apps/web/src/i18n/locales/hu.ts @@ -407,6 +407,7 @@ export const hu: Dict = { 'examples.modeDeck': 'Diák', 'examples.modeDocument': 'Dokumentumok és sablonok', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'Élő', 'examples.scenarioGeneral': 'Általános', 'examples.scenarioEngineering': 'Mérnöki', 'examples.scenarioProduct': 'Termék', diff --git a/apps/web/src/i18n/locales/id.ts b/apps/web/src/i18n/locales/id.ts index 36ebcb859..79ccc194a 100644 --- a/apps/web/src/i18n/locales/id.ts +++ b/apps/web/src/i18n/locales/id.ts @@ -511,6 +511,7 @@ export const id: Dict = { 'examples.modeDeck': 'Slide', 'examples.modeDocument': 'Dokumen & templat', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'Langsung', 'examples.scenarioGeneral': 'Umum', 'examples.scenarioEngineering': 'Engineering', 'examples.scenarioProduct': 'Produk', diff --git a/apps/web/src/i18n/locales/ja.ts b/apps/web/src/i18n/locales/ja.ts index 6128de499..3ba20950e 100644 --- a/apps/web/src/i18n/locales/ja.ts +++ b/apps/web/src/i18n/locales/ja.ts @@ -299,6 +299,7 @@ export const ja: Dict = { 'examples.modeDeck': 'スライド', 'examples.modeDocument': 'ドキュメント & テンプレート', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'ライブ', 'examples.scenarioGeneral': '一般', 'examples.scenarioEngineering': 'エンジニアリング', 'examples.scenarioProduct': 'プロダクト', diff --git a/apps/web/src/i18n/locales/ko.ts b/apps/web/src/i18n/locales/ko.ts index 46b015fc1..f3ac54b60 100644 --- a/apps/web/src/i18n/locales/ko.ts +++ b/apps/web/src/i18n/locales/ko.ts @@ -407,6 +407,7 @@ export const ko: Dict = { 'examples.modeDeck': '슬라이드', 'examples.modeDocument': '문서 및 템플릿', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': '라이브', 'examples.scenarioGeneral': '일반', 'examples.scenarioEngineering': '엔지니어링', 'examples.scenarioProduct': '제품', diff --git a/apps/web/src/i18n/locales/pl.ts b/apps/web/src/i18n/locales/pl.ts index 748394277..a6ddcb0a4 100644 --- a/apps/web/src/i18n/locales/pl.ts +++ b/apps/web/src/i18n/locales/pl.ts @@ -407,6 +407,7 @@ export const pl: Dict = { 'examples.modeDeck': 'Slajdy', 'examples.modeDocument': 'Dokumenty i szablony', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'Live', 'examples.scenarioGeneral': 'Ogólne', 'examples.scenarioEngineering': 'Inżynieria', 'examples.scenarioProduct': 'Produkt', diff --git a/apps/web/src/i18n/locales/pt-BR.ts b/apps/web/src/i18n/locales/pt-BR.ts index 22092b744..d70d923a7 100644 --- a/apps/web/src/i18n/locales/pt-BR.ts +++ b/apps/web/src/i18n/locales/pt-BR.ts @@ -417,6 +417,7 @@ export const ptBR: Dict = { 'examples.modeDeck': 'Slides', 'examples.modeDocument': 'Docs e templates', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'Ao vivo', 'examples.scenarioGeneral': 'Geral', 'examples.scenarioEngineering': 'Engenharia', 'examples.scenarioProduct': 'Produto', diff --git a/apps/web/src/i18n/locales/ru.ts b/apps/web/src/i18n/locales/ru.ts index 5c480663f..902760b04 100644 --- a/apps/web/src/i18n/locales/ru.ts +++ b/apps/web/src/i18n/locales/ru.ts @@ -417,6 +417,7 @@ export const ru: Dict = { 'examples.modeDeck': 'Презентации', 'examples.modeDocument': 'Документы и шаблоны', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'Live', 'examples.scenarioGeneral': 'Общее', 'examples.scenarioEngineering': 'Инженерия', 'examples.scenarioProduct': 'Продукт', diff --git a/apps/web/src/i18n/locales/tr.ts b/apps/web/src/i18n/locales/tr.ts index 68a44e563..0f13c5e80 100644 --- a/apps/web/src/i18n/locales/tr.ts +++ b/apps/web/src/i18n/locales/tr.ts @@ -400,6 +400,7 @@ export const tr: Dict = { 'examples.modeDeck': 'Slaytlar', 'examples.modeDocument': 'Doküman & şablonlar', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'Canlı', 'examples.scenarioGeneral': 'Genel', 'examples.scenarioEngineering': 'Mühendislik', 'examples.scenarioProduct': 'Ürün', diff --git a/apps/web/src/i18n/locales/uk.ts b/apps/web/src/i18n/locales/uk.ts index 4d538ea38..81dcf88fa 100644 --- a/apps/web/src/i18n/locales/uk.ts +++ b/apps/web/src/i18n/locales/uk.ts @@ -418,6 +418,7 @@ export const uk: Dict = { 'examples.modeDeck': 'Слайди', 'examples.modeDocument': 'Документи та шаблони', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': 'Live', 'examples.scenarioGeneral': 'Загальне', 'examples.scenarioEngineering': 'Інженерія', 'examples.scenarioProduct': 'Продукт', diff --git a/apps/web/src/i18n/locales/zh-CN.ts b/apps/web/src/i18n/locales/zh-CN.ts index 92fe484b2..08e121087 100644 --- a/apps/web/src/i18n/locales/zh-CN.ts +++ b/apps/web/src/i18n/locales/zh-CN.ts @@ -412,6 +412,7 @@ export const zhCN: Dict = { 'examples.modeDeck': '幻灯片', 'examples.modeDocument': '文档与模板', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': '实时', 'examples.scenarioGeneral': '通用', 'examples.scenarioEngineering': '工程', 'examples.scenarioProduct': '产品', diff --git a/apps/web/src/i18n/locales/zh-TW.ts b/apps/web/src/i18n/locales/zh-TW.ts index 968a30491..74ba796c2 100644 --- a/apps/web/src/i18n/locales/zh-TW.ts +++ b/apps/web/src/i18n/locales/zh-TW.ts @@ -412,6 +412,7 @@ export const zhTW: Dict = { 'examples.modeDeck': '投影片', 'examples.modeDocument': '文件與範本', 'examples.modeOrbit': 'Orbit', + 'examples.modeLive': '即時', 'examples.scenarioGeneral': '通用', 'examples.scenarioEngineering': '工程', 'examples.scenarioProduct': '產品', diff --git a/apps/web/src/i18n/types.ts b/apps/web/src/i18n/types.ts index 2a5fdf26e..2c4239399 100644 --- a/apps/web/src/i18n/types.ts +++ b/apps/web/src/i18n/types.ts @@ -570,6 +570,7 @@ export interface Dict { 'examples.modeDeck': string; 'examples.modeDocument': string; 'examples.modeOrbit': string; + 'examples.modeLive': string; 'examples.scenarioGeneral': string; 'examples.scenarioEngineering': string; 'examples.scenarioProduct': string; diff --git a/packages/contracts/src/api/registry.ts b/packages/contracts/src/api/registry.ts index 3c2a528fe..6980c07bb 100644 --- a/packages/contracts/src/api/registry.ts +++ b/packages/contracts/src/api/registry.ts @@ -45,6 +45,14 @@ export interface SkillSummary { craftRequires?: string[]; hasBody: boolean; examplePrompt: string; + // True when this skill exists only to group derived `<parent>:<child>` + // example cards. The Examples gallery hides such cards because their + // preview would duplicate one of the derived cards and add no extra + // information, but the entry stays in the listing so `findSkillById` + // resolves the parent for system-prompt composition and "Use this + // prompt" fast-create on a derived card still composes the parent's + // SKILL.md body. + aggregatesExamples?: boolean; } export interface SkillDetail extends SkillSummary { diff --git a/skills/live-artifact/SKILL.md b/skills/live-artifact/SKILL.md index 6ef0a8895..166dfc033 100644 --- a/skills/live-artifact/SKILL.md +++ b/skills/live-artifact/SKILL.md @@ -14,6 +14,7 @@ triggers: - "实时看板" od: mode: prototype + scenario: live preview: type: html entry: index.html diff --git a/skills/live-artifact/examples/baby-health-live.html b/skills/live-artifact/examples/baby-health-live.html new file mode 100644 index 000000000..ec8ce79ee --- /dev/null +++ b/skills/live-artifact/examples/baby-health-live.html @@ -0,0 +1,761 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8" /> +<meta name="viewport" content="width=device-width, initial-scale=1" /> +<title>Mira's First Month · A quiet panel + + + + + + + +
+
+ a quiet panel from + quiver +
+ +
+ +
+ + +
+
Mira's first month · 2026
+

Mira Avery

+
28 days & counting
+
+ Born April 9 · 2026 + · + 3.42 kg · 50.8 cm + · + Mom & dad have her back +
+
+ + +
+
+

i.How she slept last night

+ Huckleberry· synced 6:42 AM +
+
+
+ + + + + + + + + + + + + + + + + + 12 am + 6 + 12 pm + 6 + + + + + + + + + + + + + + 14h 22m + total · 24h window + + + + +
+
+
Last night · 7:30 PM → 6:30 AM
+
9h 14m
+
3 wakes (down from 4) · longest stretch 3h 12m
+
"She slept 22 minutes longer per night this week. Not a streak — a rhythm."
+
+
+
+ + +
+
+

ii.How she ate today

+ Baby Tracker + scale· last 24h +
+
+
+
+ +
2:00 AM · 90ml bottle
+
5:00 AM · breast 18 min
+
8:00 AM · 110ml bottle
+
11:00 AM · breast 22 min
+
2:00 PM · 100ml bottle
+
Now · 14:32
+
5:00 PM · breast 16 min (est.)
+
8:00 PM · 100ml bottle (est.)
+
11:30 PM · breast (est.)
+
+
+ 12am4am8amnoon4pm8pm12am +
+
+ Bottle 4 today · 400 ml + Breast 5 today · ~110 min + Now +
+
+
+
+ + +
+
+

iii.How she's growing

+ Withings smart scale· weekly +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + birth + wk 1 + wk 2 + wk 3 + wk 4 + wk 5 + today + + + on track ✓ + + + 5.0 + 4.5 + 4.0 + 3.5 + + +
+
+
Weight today
+
4.84 kg
+
62nd percentile · WHO
+
+
+
Length
+
53.4 cm
+
58th percentile · WHO
+
+
+
Gain · 7 days
+
+218 g
+
healthy range (150–280g)
+
+
+
+
+ + +
+
+

iv.Today's gentle reminders

+ 4 things, no alarms +
+
+
+
Next feed
+
in ~38 min
+
Last feed at 2:00 PM (100 ml bottle). Suggested 100–110 ml.
+
+
+
Next likely nap
+
3:30 — 5:00 PM
+
She tends to fade ~90 min after morning rouse. Recent pattern.
+
+
+
Doctor visit
+
Friday 10:30 AM
+
1-month checkup with Dr. Wei. We'll bring the weight log.
+
+
+
~ Bath night
+
tonight, 6:30 PM
+
Every other day · grandma's lullaby works.
+
+
+
+ + +
+
+

v.What we noticed this week

+ Quiver· soft patterns +
+
+
+ + Quiver insights · weekly +
+

She's settling into a longer-stretch sleep at night.

+

Five small patterns matched up this week. None demand action — they just paint a picture.

+
+
Longer first stretch: first sleep block grew from 2h 14m → 3h 12m on average. good for you both
+
Feed-then-sleep latency dropped to 14 min (was 28 min in week 2). She's calmer post-feed.
+
Bottle preference stabilized on the slow-flow Dr. Brown's after the size-2 trial. No spit-up since Monday.
+
62nd percentile on weight, up from 58th — within healthy range. Length is steady at 58th.
+
Diaper count averaged 8 per day (target ≥6). Hydration is comfortable.
+
+
+
+ + +
+
+

vi.This week, in pictures

+ iCloud Photos· auto-curated +
+
+
+
Mon
+
first finger-grip
+
+
+
Wed
+
sun nap on the porch
+
+
+
Thu
+
grandma met her
+
+
+
Sat
+
first real smile (we think)
+
+
+
+ +
+ "What you measure, you remember."
+ — quiver, a quiet panel · auto-refreshes daily at 6:30 AM +
+ +
+ +
+ + + + + diff --git a/skills/live-artifact/examples/competitor-radar-live.html b/skills/live-artifact/examples/competitor-radar-live.html new file mode 100644 index 000000000..737f9f4f8 --- /dev/null +++ b/skills/live-artifact/examples/competitor-radar-live.html @@ -0,0 +1,1006 @@ + + + + + +Quiver · Competitor Radar + + + + + + + + +
+ Systemnominal · 5 sources active + Threat levelelevated + Signals (24h)42 + Last scan3m ago + Watchlist5 competitors · 18 keywords +
+ + +
+
+ +
quiver
+
+
Competitor Intelligence
+
Q2 Watch · pmtools.dev cohort
+
+
+
+ + +
+
+ +
+ + +
+
+
+
+
Active surveillance
+
Distance = threat level · Color = trend
+
+
FRAME 04 / 09
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + Critical + High + Watch + + + + + + + + + + + + +
+ L + + Linear +
+
+ N + + Notion +
+
+ A + + Asana +
+
+ M + + Monday +
+
+ C + + ClickUp +
+
+ +
+ Critical · move now + Watch · weekly + Stable +
+
+ + +
+
+
+
Selected · 04 May ↗ 06 May
+

L Linear

+
+ Critical · 92 +
+
+
+
Pricing
+
$8 → $10
+
+25% Pro · 2d ago
+
+
+
Releases (30d)
+
7
+
+3 vs cohort avg
+
+
+
Hiring
+
14 open roles
+
+8 in 30d · GTM heavy
+
+
+
X / Twitter velocity
+
142k impr/d
+
+62% vs 30d avg
+
+
+
+

Quiver synthesis · why this matters

+
+ Linear is hardening pricing into the AI-features bundle. The Pro plan jumped +25% with two announced AI features (auto-triage, code-search). Their hiring pipeline is GTM-heavy (8 of 14 new) — suggests outbound push, not inbound. Counter-move: emphasize cost-of-AI-features in our positioning and pre-empt with a 2-week migration tool. +
+
+
+ + + +
+
+
+ + +
+
+ + + + + + +
+
42 events · sorted by impact
+
+ + +
+ + +
+
+ Activity feed + Live · 24h window +
+
+
+ + +
+ +
+
+ Quiver AI synthesis + Updated 14 min ago +
+
+
Quiver intel
+

The cohort just made two converging moves on AI features.

+

Linear and Monday both raised Pro pricing with AI-bundle messaging. ClickUp's enterprise outbound stepped up +62% in social. Notion stayed quiet — possibly preparing a larger announcement.

+
+
01
Pre-empt the AI-bundle frame — publish "AI features included, no markup" landing page this week.
+
02
Migration tool from Linear → us — historical pattern: 2-week post-price-hike migration windows convert 3-5×.
+
03
Watch Notion next 7 days — silence + GTM hires usually precedes a major launch (last seen Sep '25 calendar release).
+
+
+
+ +
+
+ Competitor scorecard + Click to focus +
+
+
+ +
+
+ +
+ +
+ + + + + diff --git a/skills/live-artifact/examples/crm-table-live.html b/skills/live-artifact/examples/crm-table-live.html new file mode 100644 index 000000000..cc8e4e833 --- /dev/null +++ b/skills/live-artifact/examples/crm-table-live.html @@ -0,0 +1,1174 @@ + + + + + +Quiver · Q2 Pipeline (CRM) + + + + + + + + +
+
+ +
quiver
+
+ Workspace + / + CRM + / + Q2 Pipeline +
+
+
+ + + + PT +
+
+ + +
+
+ + Filled from Gmail · 27 leads extracted from past 30 days · Last sync 2m ago +
+
+ + + +
+
+ + +
+
+ + + + +
+
+ + + Search records… + K + + + + +
+
+ + +
+ +
+ + +
+ + + + + + + + + + + + + + +
CompanyStage$ARR📅Last contact👤OwnerNext actionThreadsEngagement
+
+ + +
+
+
+
+ Lead cold inbound + 0 +
+
+
+
+
+ Qualified 2-call + 0 +
+
+
+
+
+ Demo scheduled + 0 +
+
+
+
+
+ Proposal redlining + 0 +
+
+
+
+
+ Closed-Won live + 0 +
+
+
+
+
+ + +
+ +
+ + +
+
+
+
May2026
+
+ + + +
+
+
+
+
+ +
+ + + + +
+ + +
+ + + + + diff --git a/skills/live-artifact/examples/crypto-dashboard.html b/skills/live-artifact/examples/crypto-dashboard.html new file mode 100644 index 000000000..46d010500 --- /dev/null +++ b/skills/live-artifact/examples/crypto-dashboard.html @@ -0,0 +1,2316 @@ + + + + + +Quiver · Crypto Live Portfolio + + + + + + + + +
+
+ BTC68,412▲ 1.84% + ETH3,612▲ 2.10% + SOL186.42▲ 4.62% + BNB612.40▲ 0.94% + XRP0.5824▼ 0.42% + ADA0.4318▲ 1.20% + DOGE0.1842▲ 6.84% + AVAX36.84▲ 2.86% + LINK14.92▲ 1.40% + DOT7.18▼ 0.62% + SUI2.18▲ 8.40% + NEAR5.42▲ 3.20% + TIA7.84▼ 1.18% + ARB0.6420▲ 0.95% + + BTC68,412▲ 1.84% + ETH3,612▲ 2.10% + SOL186.42▲ 4.62% + BNB612.40▲ 0.94% + XRP0.5824▼ 0.42% + ADA0.4318▲ 1.20% + DOGE0.1842▲ 6.84% + AVAX36.84▲ 2.86% + LINK14.92▲ 1.40% + DOT7.18▼ 0.62% + SUI2.18▲ 8.40% + NEAR5.42▲ 3.20% + TIA7.84▼ 1.18% + ARB0.6420▲ 0.95% +
+
+ + +
+ Block287,418,612 + Gas12 gwei· low + Mempool142,840 tx + BTC hash612 EH/s + DeFi TVL$84.2B + ETH staking28.4% (34M ETH) + Fear & Greed78 · Greed + Stables MC$172.8B +
+ + +
+
+ +
quiver
+
Crypto
+ +
+
+ + + 24/7 markets · 14:32:18 UTC + + + + Search ticker, news, analyst… + K + + + PT +
+
+ +
+ + +
+
+
+
+
Total crypto net worth · USD
+
Coinbase · Ledger · MetaMask   On-chain live
+
+
+ + + + + + + +
+
+ +
+ $186,420.18 +
+
+ +$5,842.16 today · +3.23% + +$112,420.18 all time · +151.9% + vs BTC +22.4pp +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ May '24JulSepNovJan '25MarToday +
+
+
+ +
+
+
24h P&L
+
+$5,842.16
+
+3.23% · 9 green / 2 red
+
+
+
Stables · dry powder
+
$24,180
+
USDC 18.4k · USDT 5.78k · 4.92% APY
+
+
+
Best holding · SOL
+
+812.4%
+ + + + + +
+
+
Alpha vs BTC · YTD
+
+22.4pp
+
Sharpe 1.86 · Vol 64% · Max DD −34%
+
+
+
+ + +
+ +
+
+
+
+
+ +
Bitcoin · L1 · Mkt cap $1.35T · Dominance 54.2%
+
+
+
+
$68,412
+
▲ +$1,236.40   +1.84% 24h
+
+
+ +
+
24h open
67,176
+
24h high
68,920
+
24h low
66,840
+
24h volume
$28.4B
+
Circ supply
19.74M / 21M
+
ATH
$73,750 (Mar)
+
+ +
+
+
+ Candles + Line + Volume + SMA 50 + RSI +
+
+ + + + + + +
+
+ + + + + + + + + + + + + 940 + 900 + 860 + 820 + 780 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BUY · 60 @ 812 + + BUY · 40 @ 845 + + + + + + + 924.31 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VOL + 38.4M + +
+
+ + +
+
+
+
Quiver AI · Signal #2417
+ +
+
+ Rotate stables → SOL on DePIN + Firedancer accelerator. +
+
+ On-chain activity is breaking out while ETF spot demand is steady. Risk-reward favors adding SOL while keeping BTC core untouched. +
+
+ Trend▲ Bullish + Funding+0.012% 8h + ETF flow+$418M / 5d + SentimentGreed 78 +
+
+ +
+
+
Conviction
+
82%
+
+
+
+
3-mo target
+
$240
+
+28.7% upside
+
+
+
Suggested size
+
+45 SOL
+
≈ $8,389 · 4.5% port
+
+
+ + +
+ + + + +
+ + +
+
+

Why now · 3 catalysts

+
+
+
01
+
Firedancer mainnet imminent — Jump's validator client is in canary on testnet. Once live, expect a step-change in throughput narrative. Historical analogs (Sui mainnet, Sept '23) priced 28% in 14 days.
+
+
+
02
+
DePIN flywheel printing real revenue — Helium, Render, Hivemapper combined paid $32M+ in last quarter. Settlement is on Solana; fee accrual is sticky.
+
+
+
03
+
BTC ETF flows holding — net +$418M over the last 5 sessions. Macro tape is risk-on; BTC dominance compressing — usually a green light for L1 rotation.
+
+
+
+
+ + +
+
+
+
High
+
Mainnet outage. Solana has had 6 partial halts in 2 years. A Firedancer rollout could either harden or briefly fracture the network.
+
P 24%
+
+
+
Med
+
FTX estate distributions. ~14M SOL still unlocked to creditors over 18 months. Recent prints have been absorbed; a fast-paced sale would pressure spot 8–12%.
+
P 32%
+
+
+
Med
+
L2 vs L1 narrative whipsaw. If Base/Arbitrum capture re-flips dominance, SOL beta to ETH could turn negative for a quarter.
+
P 22%
+
+
+
Low
+
Macro risk-off. Crypto beta to NDX is ~2.4. A 5% NDX correction would mark down crypto book ~12% even on unchanged thesis. Use stables ladder as buffer.
+
P 18%
+
+
+
+ + +
+
+
Avg cost basis · SOL$20.46 · 240 SOL
+
Mark-to-market$44,740 (+812.4%)
+
Holding period692 days · earned 18.4 SOL staking
+
Position vs target24.0% / 30% target — room to add
+
WalletPhantom · Ledger Nano X
+
Beta to BTC1.84
+
+
"Conviction trade in a portfolio that's already worked. Don't let recency bias trim a thesis that's strengthening on every datapoint."
+ Quiver AI flags +$418M ETF inflows + Firedancer testnet as the recent regime change. Funding is calm (+0.012%) — no froth — and on-chain DAU just hit a 90-day high. The cleanest add is on a −5% wick into $176; at-mkt is acceptable. +
+
+
+ + +
+
+
+
+
Entry zone
+
$182 — $190  at-mkt OK
+
+
+
+
Add lot 2
+
$176 on -5% wick  +30 SOL
+
+
+
+
Invalidation
+
$162 daily close  −13.0% from spot
+
+
+
+
Target 1
+
$220 trim 25%  +18.0%
+
+
+
+
Target 2
+
$240 trim 25%  +28.7%
+
+
+
+
Cycle target
+
$295 trim 50%  +58.2%
+
+
+
+ +
+ + + +
+
+
+ + +
+
+
Holdings · 9 wallets
+
On-chain · last sync 14:32:18 UTC
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AssetAmountAvg costLastTrend · 30dMarket valueUnrealized P&LAllocation
+
+ + + BTC Coinbase ↗ + Bitcoin · Ledger Nano X + +
+
1.084228,44068,412 + + $74,170+$43,738+143.7%  39.8%
+
+ + + SOL Coinbase ↗ + Solana · Phantom (staked 18.4) + +
+
240.0020.46186.42 + + $44,740+$39,830+812.4%  24.0%
+
+ Ξ + + ETH Coinbase ↗ + Ethereum · MetaMask · 8.4 staked + +
+
10.621,8203,612 + + $38,360+$19,032+98.5%  20.6%
+
+ L + + LINK Coinbase ↗ + Chainlink · MetaMask + +
+
6808.4214.92 + + $10,146+$4,420+77.2%  5.4%
+
+ A + + AVAX Coinbase ↗ + Avalanche · Core wallet + +
+
22028.4036.84 + + $8,105+$1,857+29.7%  4.4%
+
+ + + DOT Coinbase ↗ + Polkadot · Polkadot.js + +
+
6009.107.18 + + $4,308−$1,152−21.1%  2.3%
+
+ Ð + + DOGE Coinbase ↗ + Dogecoin · Hot wallet + +
+
12,4000.0820.1842 + + $2,284+$1,267+124.6%  1.2%
+
+ $ + + USDC stables + Coinbase + Aave (4.92% APY) + +
+
18,4201.001.00$18,420+$842 yield  9.9%
+
+ T + + USDT stables + Tron · cold storage + +
+
5,7801.001.00$5,780  3.1%
+
+ + +
+
+
+
Watchlist · L1 + L2 + DePIN
+
14 tickers · 14:32 UTC
+
+
+
+
+
SUI
Sui Network
+ ▲ 8.40% +
+
$2.18
+
+ + + + + +
+
+ Vol $812M + Coinbase ↗ +
+
+ +
+
+
DOGE
Dogecoin
+ ▲ 6.84% +
+
$0.1842
+
+ + + + +
+
+ Vol $1.4B + Coinbase ↗ +
+
+ +
+
+
AVAX
Avalanche
+ ▲ 2.86% +
+
$36.84
+
+ + + + +
+
+ Vol $640M + Coinbase ↗ +
+
+ +
+
+
NEAR
NEAR Protocol
+ ▲ 3.20% +
+
$5.42
+
+ + + + +
+
+ Vol $312M + Coinbase ↗ +
+
+ +
+
+
TIA
Celestia
+ ▼ 1.18% +
+
$7.84
+
+ + + + + +
+
+ Vol $84M + Coinbase ↗ +
+
+ +
+
+
ARB
Arbitrum
+ ▲ 0.95% +
+
$0.6420
+
+ + + + +
+
+ Vol $186M + Coinbase ↗ +
+
+ +
+
+
BNB
BNB Chain
+ ▲ 0.94% +
+
$612.40
+
+ + + + +
+
+ Vol $920M + Coinbase ↗ +
+
+ +
+
+
XRP
Ripple
+ ▼ 0.42% +
+
$0.5824
+
+ + + + +
+
+ Vol $1.2B + Coinbase ↗ +
+
+
+
+ + +
+
+
Live signals & news
+
Filtered to your book
+
+
+
+
14:31
+
+
AI On-chain anomaly
+
Solana DEX volume rolling 7d up +62% — Jupiter + Raydium printing record fee days. Settlement layer thesis intact.
+
SOLJUP
+
+ + Bull +
+
+
14:18
+
+
CoinDesk
+
Spot BTC ETFs see +$418M net inflows over 5 sessions; IBIT alone takes 70%, FBTC 22%.
+
BTCIBIT
+
+ + Bull +
+
+
13:54
+
+
The Block
+
Firedancer canary deployment hits 1.2M TPS in stress test — full mainnet rollout targeted Q4.
+
SOL
+
+ + Bull +
+
+
13:30
+
+
AI Funding tracker
+
Perp funding turning slightly positive across L1s; ETH 8h funding now +0.014%. Watch for crowding on next 5% leg.
+
ETHSOL
+
+ Watch +
+
+
12:48
+
+
Bloomberg
+
SEC granted certificate of effectiveness on spot ETH staking ETF filing — issuer confirmation expected within 7 days.
+
ETH
+
+ + Bull +
+
+
11:22
+
+
AI Whale flow
+
12,400 ETH moved from Genesis estate cold wallet to OKX — 6h. Possible distribution; watch order books.
+
ETH
+
+ High risk +
+
+
+
+ +
+ + +
+ +
+
+ On-chain feed — block 287,418,612 + Quotes via Coinbase + CoinGecko · Glassnode for on-chain +
+
+ Quiver Crypto · v1.4.0 + Source: Coinbase + Phantom + Ledger linked + Last sync 14:32:18 UTC +
+
+ + + + + diff --git a/skills/live-artifact/examples/monday-operator-live.html b/skills/live-artifact/examples/monday-operator-live.html new file mode 100644 index 000000000..0b466c19c --- /dev/null +++ b/skills/live-artifact/examples/monday-operator-live.html @@ -0,0 +1,919 @@ + + + + + +Monday morning briefing · Quiver + + + + + + + +
+
+
Daily Briefing · Volume IV · Issue 18
+
+ The Monday Memo + — from quiver +
+
+
+ 5 sources connected · synced 6:42 AM + + +
+
+ +
+ +
+ + +
+
Monday · May 6, 2026 · 8:42 AM
+

Good morning, PT.

+
+ Six things deserve your attention this morning. The rest can wait until coffee #2. +
+
+ San Francisco· + 62° light fog, clearing 11 AM· + Sunset 8:09 PM· + 7h 24m sleep +
+
+ + +
+
+

i.This week's revenue

+ Stripe · 7-day rolling +
+
+
+
Past 7 days
+
$84,210
+
+12.4% vs prior week · +$9,316
+
Driven by 3 enterprise expansions on Wed–Thu. Two SMB churns absorbed in net new.
+
+
+ + + + + + + + + + + + + + + + + + + Mon + Tue + Wed + Thu + Fri + Sat + Sun + Mon + + + + today + +
+
+
+ + +
+
+

ii.Customers who deserve a reply

+ Gmail · curated by relevance +
+
+
+ + +
+
+

iii.What's stuck on the team

+ Linear · idle > 48h +
+
+
+ + +
+
+

iv.Today, hour by hour

+ Google Calendar · 6 events +
+
+
+ + +
+
+

v.Pull requests waiting on you

+ GitHub · 4 PRs +
+
+
+ + +
+
Yesterday, quietly:
+
+
Voltage Co. went live in production at 6:14 PM. Zero rollback. Onboarding kickoff tomorrow.
+
Mira closed Atlas Cooperative ($240k) on a Sunday call. The handshake email landed at 11:47 PM.
+
QA cleared the Q2 release — 0 P0s, 2 P3s. Cleanest cut in 7 quarters.
+
Nora's onboarding wrapped a week ahead. Already shipping in src/agent/router.
+
Stripe weekly closed at $84.2k — fourth straight week above $80k. Comfortable Q2 trajectory.
+
Sam shipped the auth migration with a 54-line PR. The rollback plan was three commits.
+
+
+ +
+ + + + +
+ +
+ + + + + diff --git a/skills/live-artifact/examples/stock-dashboard.html b/skills/live-artifact/examples/stock-dashboard.html new file mode 100644 index 000000000..21d2f70a5 --- /dev/null +++ b/skills/live-artifact/examples/stock-dashboard.html @@ -0,0 +1,2246 @@ + + + + + +Quiver · Live Portfolio + + + + + + + + +
+
+ SPX5,827.04▲ 0.42% + NDX20,743.18▲ 0.71% + DJI42,418.23▼ 0.18% + VIX14.62▼ 3.40% + NVDA924.31▲ 2.41% + AAPL228.74▲ 0.84% + MSFT438.12▲ 1.06% + TSLA312.55▼ 1.93% + META596.18▲ 1.42% + GOOGL186.40▲ 0.55% + AMZN218.94▲ 0.79% + AMD142.06▼ 0.51% + BTC68,412▲ 1.84% + ETH3,612▲ 2.10% + + SPX5,827.04▲ 0.42% + NDX20,743.18▲ 0.71% + DJI42,418.23▼ 0.18% + VIX14.62▼ 3.40% + NVDA924.31▲ 2.41% + AAPL228.74▲ 0.84% + MSFT438.12▲ 1.06% + TSLA312.55▼ 1.93% + META596.18▲ 1.42% + GOOGL186.40▲ 0.55% + AMZN218.94▲ 0.79% + AMD142.06▼ 0.51% + BTC68,412▲ 1.84% + ETH3,612▲ 2.10% +
+
+ + +
+
+ +
quiver
+
Live
+ +
+
+ + + NYSE Open · 14:32:18 EST + + + + Search ticker, news, analyst… + K + + + PT +
+
+ +
+ + +
+
+
+
+
Total portfolio value · USD
+
All accounts   Live
+
+
+ + + + + + + +
+
+ +
+ $284,521.64 +
+
+ +$3,182.40 today · +1.13% + +$84,521.64 all time · +42.3% + vs S&P +18.7pp +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ May '24JulSepNovJan '25MarToday +
+
+
+ +
+
+
Today's P&L
+
+$3,182.40
+
+1.13% · 8 winners / 2 losers
+
+
+
Buying power
+
$12,438.21
+
Cash + 2× margin available
+
+
+
Best position · NVDA
+
+187.4%
+ + + + + +
+
+
Alpha vs S&P 500 · YTD
+
+18.7pp
+
Sharpe 1.42 · Beta 1.18 · Max DD −9.4%
+
+
+
+ + +
+ +
+
+
+
N
+
+ +
NVIDIA Corporation · Semiconductors · Mkt cap $2.27T
+
+
+
+
$924.31
+
▲ +$21.74   +2.41% today
+
+
+ +
+
Open
906.80
+
High
929.18
+
Low
902.44
+
Volume
38.42M
+
P/E
62.4
+
52W range
412 — 974
+
+ +
+
+
+ Candles + Line + Volume + SMA 50 + RSI +
+
+ + + + + + +
+
+ + + + + + + + + + + + + 940 + 900 + 860 + 820 + 780 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BUY · 60 @ 812 + + BUY · 40 @ 845 + + + + + + + 924.31 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VOL + 38.4M + +
+
+ + +
+
+
+
Quiver AI · Signal #2417
+ +
+
+ Add to NVDA on Blackwell ramp + AI capex inflection. +
+
+ Three converging catalysts and resilient channel checks. Conviction has stepped up since last week's hyperscaler guidance. +
+
+ Trend▲ Bullish + VolCompressed + Breadth76% above 50DMA + SentimentGreed 74 +
+
+ +
+
+
Conviction
+
87%
+
+
+
+
12-mo target
+
$1,050
+
+13.6% upside
+
+
+
Suggested size
+
+12 sh
+
≈ $11,092 · 4.2% port
+
+
+ + +
+ + + + +
+ + +
+
+

Why now · 3 catalysts

+
+
+
01
+
Blackwell shipments accelerating — Foxconn and Wiwynn guidance both implied an Aug volume step-up. Backlog visibility extends through Q1 '26.
+
+
+
02
+
Hyperscaler capex revised up — MSFT, META, AMZN combined FY '25 capex now +34% YoY. AI infra is the binding allocation.
+
+
+
03
+
Sentiment de-risked — short interest at 6-month high but RSI cooling from overbought. Setup mirrors the May '24 base before the +47% leg.
+
+
+
+
+ + +
+
+
+
High
+
China export-control re-tightening. A new BIS rule on H20 derivatives could land in Q3 '25 and remove ~12% of FY '26 revenue.
+
P 28%
+
+
+
Med
+
Custom-silicon share gain. AWS Trainium 3 + Google TPU v6 uptake could pressure 2H '25 mix; channel checks suggest replacement, not displacement, for now.
+
P 35%
+
+
+
Med
+
Hyperscaler air-pocket. If MSFT pauses one data-center wave (precedent: Mar '23), backlog re-rates by 2 quarters. Watch capex commentary.
+
P 22%
+
+
+
Low
+
Multiple compression. NTM P/E at 36× is rich vs 5y avg 28×. Soft macro print could trim 15–18% off price without earnings damage.
+
P 18%
+
+
+
+ + +
+
+
Avg cost basis$321.55 · 100 sh
+
Mark-to-market$92,431 (+187.4%)
+
Holding period418 days · LT cap-gains eligible
+
Position vs target32.5% / 35% target — room to add
+
Realized YTD$8,420 (1 trim, May)
+
Beta to NDX1.42
+
+
+
"You're still under your target weight. The May trim was good discipline, but the thesis hasn't changed — it's strengthened."
+ Quiver AI considers the Aug Foxconn data point a binary upgrade: it shifted conviction from 78% to 87% in one print. Holding through earnings remains the higher-EV path; trimming above $1,000 is the suggested first scale-out. +
+
+
+ + +
+
+
+
+
Entry zone
+
$905 — $928  at-mkt OK
+
+
+
+
Add lot 2
+
$880 on -5% pullback  +8 sh
+
+
+
+
Stop level
+
$842 close-only  −8.9% from spot
+
+
+
+
Target 1
+
$1,005 trim 25%  +8.7%
+
+
+
+
Target 2
+
$1,050 trim 25%  +13.6%
+
+
+
+
Target 3
+
$1,140 trim 50%  +23.3%
+
+
+
+ +
+ + + +
+
+
+ + +
+
+
Holdings · 9 positions
+
Live · last quote 14:32:18 EST
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SymbolSharesAvg costLastTrend · 30dMarket valueUnrealized P&LAllocation
+
+ N + + NVDA NASDAQ ↗ + NVIDIA Corporation + +
+
100321.55924.31 + + $92,431+$60,276+187.4%  32.5%
+
+ A + + AAPL NASDAQ ↗ + Apple Inc. + +
+
220152.10228.74 + + $50,323+$16,861+50.4%  17.7%
+
+ M + + MSFT NASDAQ ↗ + Microsoft Corp. + +
+
85328.40438.12 + + $37,240+$9,326+33.4%  13.1%
+
+ M + + META NASDAQ ↗ + Meta Platforms + +
+
55312.20596.18 + + $32,790+$15,619+91.0%  11.5%
+
+ G + + GOOGL NASDAQ ↗ + Alphabet Class A + +
+
160128.90186.40 + + $29,824+$9,200+44.6%  10.5%
+
+ A + + AMZN NASDAQ ↗ + Amazon.com Inc. + +
+
90142.50218.94 + + $19,705+$6,880+53.6%  6.9%
+
+ T + + TSLA NASDAQ ↗ + Tesla, Inc. + +
+
42288.40312.55 + + $13,127+$1,015+8.4%  4.6%
+
+ A + + AMD NASDAQ ↗ + Advanced Micro Devices + +
+
52158.90142.06 + + $7,387−$876−10.6%  2.6%
+
+ $ + + USD Cash + Settled cash + +
+
1.00$1,694  0.6%
+
+ + +
+
+
+
Watchlist · curated
+
12 tickers · 14:32 EST
+
+
+
+
+
PLTR
Palantir
+ ▲ 4.81% +
+
$58.42
+
+ + + + + +
+
+ Vol 4.21M + NASDAQ ↗ +
+
+ +
+
+
SHOP
Shopify
+ ▲ 2.13% +
+
$112.84
+
+ + + + +
+
+ Vol 6.84M + NYSE ↗ +
+
+ +
+
+
TSM
TSMC
+ ▲ 1.62% +
+
$208.77
+
+ + + + +
+
+ Vol 12.4M + NYSE ↗ +
+
+ +
+
+
COIN
Coinbase
+ ▲ 3.48% +
+
$236.10
+
+ + + + +
+
+ Vol 8.92M + NASDAQ ↗ +
+
+ +
+
+
CRWD
CrowdStrike
+ ▼ 1.24% +
+
$352.09
+
+ + + + + +
+
+ Vol 3.24M + NASDAQ ↗ +
+
+ +
+
+
SMCI
Super Micro
+ ▲ 5.62% +
+
$48.93
+
+ + + + +
+
+ Vol 18.2M + NASDAQ ↗ +
+
+ +
+
+
UBER
Uber Technologies
+ ▲ 1.18% +
+
$72.41
+
+ + + + +
+
+ Vol 7.42M + NYSE ↗ +
+
+ +
+
+
NFLX
Netflix
+ ▼ 0.66% +
+
$728.50
+
+ + + + +
+
+ Vol 2.86M + NASDAQ ↗ +
+
+
+
+ + +
+
+
Live signals & news
+
Filtered to your book
+
+
+
+
14:31
+
+
AI Quiver Research
+
Hyperscaler capex tracker rolls forward — MSFT + META combined FY '25 guide now +34% YoY, reinforcing NVDA/SMCI thesis.
+
NVDASMCIMSFTMETA
+
+ + Bull +
+
+
14:18
+
+
Bloomberg
+
Tesla cuts Model Y prices in China by 6% as BYD pressure intensifies in Q2 sales window.
+
TSLA
+
+ High risk +
+
+
13:54
+
+
Reuters
+
FOMC minutes signal one more cut probable in Sept — yields fall 9 bps on the long end.
+
SPXTLT
+
+ + Bull +
+
+
13:30
+
+
AI Earnings whisper
+
CRWD whisper revised −2.1% after channel checks show enterprise renewal slippage.
+
CRWD
+
+ Watch +
+
+
12:48
+
+
CNBC
+
Apple confirms Vision Pro 2 component orders for Q4 — 1.4M unit run rate implied.
+
AAPLSONY
+
+ + Bull +
+
+
11:22
+
+
AI Anomaly detector
+
SMCI dark-pool prints up 220% vs 30-day baseline — likely block accumulation.
+
SMCI
+
+ Watch +
+
+
+
+ +
+ + +
+ +
+
+ Live data — refresh 1s + Quotes via IEX Cloud · Delayed 0.0s +
+
+ Quiver Live · v1.4.0 + Source: Robinhood + IBKR linked + Last sync 14:32:18 EST +
+
+ + + + + diff --git a/skills/live-artifact/examples/stock-portfolio-live/artifact.json b/skills/live-artifact/examples/stock-portfolio-live/artifact.json new file mode 100644 index 000000000..25a518ab9 --- /dev/null +++ b/skills/live-artifact/examples/stock-portfolio-live/artifact.json @@ -0,0 +1,16 @@ +{ + "title": "Quiver Live · Personal Portfolio", + "slug": "quiver-live-portfolio", + "pinned": true, + "preview": { + "type": "html", + "entry": "index.html" + }, + "document": { + "format": "html_template_v1", + "templatePath": "template.html", + "generatedPreviewPath": "index.html", + "dataPath": "data.json", + "dataJson": {} + } +} diff --git a/skills/live-artifact/examples/stock-portfolio-live/data.json b/skills/live-artifact/examples/stock-portfolio-live/data.json new file mode 100644 index 000000000..efc6baceb --- /dev/null +++ b/skills/live-artifact/examples/stock-portfolio-live/data.json @@ -0,0 +1,453 @@ +{ + "summary": { + "brand": "quiver", + "pro": "Live", + "marketState": "NYSE Open · 14:32:18 EST", + "lastSync": "14:32:18 EST", + "totalLabel": "Total portfolio value · USD", + "accountsLabel": "All accounts (Robinhood + IBKR)", + "totalValue": "$284,521.64", + "todayPnl": "+$3,182.40", + "todayPct": "+1.13%", + "todayDirClass": "up", + "todayArrow": "▲", + "allTimePnl": "+$84,521.64", + "allTimePct": "+42.3%", + "allTimeDirClass": "up", + "allTimeArrow": "▲", + "alphaLabel": "vs S&P · YTD", + "alpha": "+18.7pp", + "areaPoints": "10,150 10,128 60,124 110,116 160,118 210,108 260,114 310,98 360,90 410,96 460,80 510,72 560,76 610,60 660,54 710,46 760,40 810,32 860,28 870,28 870,150", + "linePoints": "10,128 60,124 110,116 160,118 210,108 260,114 310,98 360,90 410,96 460,80 510,72 560,76 610,60 660,54 710,46 760,40 810,32 860,28", + "markerX": "860", + "markerY": "28", + "axisTicks": [ + { "label": "May '24" }, + { "label": "Jul" }, + { "label": "Sep" }, + { "label": "Nov" }, + { "label": "Jan '25" }, + { "label": "Mar" }, + { "label": "Today" } + ] + }, + + "kpis": [ + { + "label": "Today's P&L", + "value": "+$3,182.40", + "valueClass": "up", + "sub": "+1.13% · 8 winners / 2 losers", + "subClass": "up" + }, + { + "label": "Buying power", + "value": "$12,438.21", + "valueClass": "", + "sub": "Cash + 2× margin available", + "subClass": "" + }, + { + "label": "Best position · NVDA", + "value": "+187.4%", + "valueClass": "up", + "sub": "Avg cost $321.55 · Now $924.31", + "subClass": "up" + }, + { + "label": "Alpha vs S&P 500 · YTD", + "value": "+18.7pp", + "valueClass": "alpha", + "sub": "Sharpe 1.42 · Beta 1.18 · Max DD −9.4%", + "subClass": "" + } + ], + + "featured": { + "iconText": "N", + "iconBg": "linear-gradient(135deg,#76b900,#4d8400)", + "iconFg": "#0a1502", + "symbol": "NVDA", + "name": "NVIDIA Corporation", + "sector": "Semiconductors · Mkt cap $2.27T", + "exchangeName": "NASDAQ", + "exchangeUrl": "https://www.nasdaq.com/market-activity/stocks/nvda", + "yahooUrl": "https://finance.yahoo.com/quote/NVDA", + "tradingViewUrl": "https://www.tradingview.com/symbols/NASDAQ-NVDA/", + "price": "$924.31", + "delta": "▲ +$21.74 +2.41% today", + "dirClass": "up", + "stats": [ + { "label": "Open", "value": "906.80" }, + { "label": "High", "value": "929.18" }, + { "label": "Low", "value": "902.44" }, + { "label": "Volume", "value": "38.42M" }, + { "label": "P/E", "value": "62.4" }, + { "label": "52W range", "value": "412 — 974" } + ], + "areaPoints": "40,260 40,220 80,210 120,200 160,194 200,184 240,180 280,170 320,160 360,150 400,138 440,128 480,120 520,108 560,100 600,86 640,76 680,64 720,56 760,48 800,40 840,30 850,30 850,260", + "linePoints": "40,220 80,210 120,200 160,194 200,184 240,180 280,170 320,160 360,150 400,138 440,128 480,120 520,108 560,100 600,86 640,76 680,64 720,56 760,48 800,40 840,30", + "priceY": "30", + "priceLabelY": "21", + "priceLabelTextY": "34", + "priceLabelText": "924.31" + }, + + "ai": { + "tag": "Quiver AI · Signal #2417", + "headlineAccent": "Add to NVDA", + "headlineRest": "on Blackwell ramp + AI capex inflection.", + "sub": "Three converging catalysts and resilient channel checks. Conviction has stepped up since last week's hyperscaler guidance.", + "conviction": "87%", + "convictionWidth": "87%", + "targetLabel": "12-mo target", + "target": "$1,050", + "upside": "+13.6%", + "size": "+12 sh", + "sizeSub": "≈ $11,092 · 4.2% port", + "catalysts": [ + { + "n": "01", + "title": "Blackwell shipments accelerating", + "text": "Foxconn and Wiwynn guidance both implied an Aug volume step-up. Backlog visibility extends through Q1 '26." + }, + { + "n": "02", + "title": "Hyperscaler capex revised up", + "text": "MSFT, META, AMZN combined FY '25 capex now +34% YoY. AI infra is the binding allocation." + }, + { + "n": "03", + "title": "Sentiment de-risked", + "text": "Short interest at 6-month high but RSI cooling from overbought. Setup mirrors the May '24 base before the +47% leg." + } + ] + }, + + "holdingsPanel": { + "title": "Holdings · 9 positions", + "subtitle": "Live · last quote 14:32:18 EST" + }, + "holdings": [ + { + "symbol": "NVDA", + "companyName": "NVIDIA Corporation", + "icon": "N", + "iconBg": "linear-gradient(135deg,#76b900,#3d6800)", + "iconFg": "#0a1502", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/nvda", + "shares": "100", + "avgCost": "321.55", + "last": "924.31", + "marketValue": "$92,431", + "pnl": "+$60,276", + "pnlPct": "+187.4%", + "dirClass": "up", + "alloc": "32.5%", + "allocWidth": "32%" + }, + { + "symbol": "AAPL", + "companyName": "Apple Inc.", + "icon": "A", + "iconBg": "linear-gradient(135deg,#0c0c0c,#3a3a3a)", + "iconFg": "#ffffff", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/aapl", + "shares": "220", + "avgCost": "152.10", + "last": "228.74", + "marketValue": "$50,323", + "pnl": "+$16,861", + "pnlPct": "+50.4%", + "dirClass": "up", + "alloc": "17.7%", + "allocWidth": "18%" + }, + { + "symbol": "MSFT", + "companyName": "Microsoft Corp.", + "icon": "M", + "iconBg": "linear-gradient(135deg,#0078d4,#005a9e)", + "iconFg": "#ffffff", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/msft", + "shares": "85", + "avgCost": "328.40", + "last": "438.12", + "marketValue": "$37,240", + "pnl": "+$9,326", + "pnlPct": "+33.4%", + "dirClass": "up", + "alloc": "13.1%", + "allocWidth": "13%" + }, + { + "symbol": "META", + "companyName": "Meta Platforms", + "icon": "M", + "iconBg": "linear-gradient(135deg,#1877f2,#0c5fc3)", + "iconFg": "#ffffff", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/meta", + "shares": "55", + "avgCost": "312.20", + "last": "596.18", + "marketValue": "$32,790", + "pnl": "+$15,619", + "pnlPct": "+91.0%", + "dirClass": "up", + "alloc": "11.5%", + "allocWidth": "11%" + }, + { + "symbol": "GOOGL", + "companyName": "Alphabet Class A", + "icon": "G", + "iconBg": "linear-gradient(135deg,#4285f4,#34a853)", + "iconFg": "#ffffff", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/googl", + "shares": "160", + "avgCost": "128.90", + "last": "186.40", + "marketValue": "$29,824", + "pnl": "+$9,200", + "pnlPct": "+44.6%", + "dirClass": "up", + "alloc": "10.5%", + "allocWidth": "10%" + }, + { + "symbol": "AMZN", + "companyName": "Amazon.com Inc.", + "icon": "A", + "iconBg": "linear-gradient(135deg,#ff9900,#cc7a00)", + "iconFg": "#1a0e00", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/amzn", + "shares": "90", + "avgCost": "142.50", + "last": "218.94", + "marketValue": "$19,705", + "pnl": "+$6,880", + "pnlPct": "+53.6%", + "dirClass": "up", + "alloc": "6.9%", + "allocWidth": "7%" + }, + { + "symbol": "TSLA", + "companyName": "Tesla, Inc.", + "icon": "T", + "iconBg": "linear-gradient(135deg,#cc0000,#660000)", + "iconFg": "#ffffff", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/tsla", + "shares": "42", + "avgCost": "288.40", + "last": "312.55", + "marketValue": "$13,127", + "pnl": "+$1,015", + "pnlPct": "+8.4%", + "dirClass": "up", + "alloc": "4.6%", + "allocWidth": "5%" + }, + { + "symbol": "AMD", + "companyName": "Advanced Micro Devices", + "icon": "A", + "iconBg": "linear-gradient(135deg,#ed1c24,#7a0000)", + "iconFg": "#ffffff", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/amd", + "shares": "52", + "avgCost": "158.90", + "last": "142.06", + "marketValue": "$7,387", + "pnl": "−$876", + "pnlPct": "−10.6%", + "dirClass": "down", + "alloc": "2.6%", + "allocWidth": "3%" + }, + { + "symbol": "USD", + "companyName": "Settled cash", + "icon": "$", + "iconBg": "linear-gradient(135deg,#facc15,#a17400)", + "iconFg": "#1a1100", + "exchName": "—", + "exchUrl": "#", + "shares": "—", + "avgCost": "—", + "last": "1.00", + "marketValue": "$1,694", + "pnl": "—", + "pnlPct": "—", + "dirClass": "", + "alloc": "0.6%", + "allocWidth": "1%" + } + ], + + "watchlistPanel": { + "title": "Watchlist · curated", + "subtitle": "12 tickers · 14:32 EST" + }, + "watchlist": [ + { + "symbol": "PLTR", + "companyName": "Palantir", + "price": "$58.42", + "pct": "▲ 4.81%", + "dirClass": "up", + "sparkColor": "#22e58c", + "sparkPoints": "0,28 20,30 40,26 60,24 80,28 100,22 120,18 140,20 160,14 180,10 200,6", + "volume": "4.21M", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/pltr" + }, + { + "symbol": "SHOP", + "companyName": "Shopify", + "price": "$112.84", + "pct": "▲ 2.13%", + "dirClass": "up", + "sparkColor": "#22e58c", + "sparkPoints": "0,26 20,28 40,22 60,24 80,20 100,22 120,18 140,16 160,12 180,14 200,10", + "volume": "6.84M", + "exchName": "NYSE", + "exchUrl": "https://www.nyse.com/quote/XNYS:SHOP" + }, + { + "symbol": "TSM", + "companyName": "TSMC", + "price": "$208.77", + "pct": "▲ 1.62%", + "dirClass": "up", + "sparkColor": "#22e58c", + "sparkPoints": "0,24 20,22 40,26 60,20 80,18 100,20 120,16 140,14 160,12 180,10 200,8", + "volume": "12.4M", + "exchName": "NYSE", + "exchUrl": "https://www.nyse.com/quote/XNYS:TSM" + }, + { + "symbol": "COIN", + "companyName": "Coinbase", + "price": "$236.10", + "pct": "▲ 3.48%", + "dirClass": "up", + "sparkColor": "#22e58c", + "sparkPoints": "0,28 20,30 40,26 60,28 80,22 100,18 120,20 140,14 160,10 180,12 200,6", + "volume": "8.92M", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/coin" + }, + { + "symbol": "CRWD", + "companyName": "CrowdStrike", + "price": "$352.09", + "pct": "▼ 1.24%", + "dirClass": "down", + "sparkColor": "#ff4f6d", + "sparkPoints": "0,14 20,12 40,16 60,14 80,18 100,20 120,18 140,22 160,24 180,28 200,30", + "volume": "3.24M", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/crwd" + }, + { + "symbol": "SMCI", + "companyName": "Super Micro", + "price": "$48.93", + "pct": "▲ 5.62%", + "dirClass": "up", + "sparkColor": "#22e58c", + "sparkPoints": "0,30 20,32 40,28 60,30 80,26 100,20 120,16 140,18 160,12 180,8 200,4", + "volume": "18.2M", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/smci" + }, + { + "symbol": "UBER", + "companyName": "Uber Technologies", + "price": "$72.41", + "pct": "▲ 1.18%", + "dirClass": "up", + "sparkColor": "#22e58c", + "sparkPoints": "0,24 20,26 40,22 60,20 80,22 100,18 120,16 140,18 160,14 180,12 200,12", + "volume": "7.42M", + "exchName": "NYSE", + "exchUrl": "https://www.nyse.com/quote/XNYS:UBER" + }, + { + "symbol": "NFLX", + "companyName": "Netflix", + "price": "$728.50", + "pct": "▼ 0.66%", + "dirClass": "down", + "sparkColor": "#ff4f6d", + "sparkPoints": "0,16 20,14 40,18 60,16 80,18 100,16 120,20 140,18 160,22 180,20 200,24", + "volume": "2.86M", + "exchName": "NASDAQ", + "exchUrl": "https://www.nasdaq.com/market-activity/stocks/nflx" + } + ], + + "newsPanel": { + "title": "Live signals & news", + "subtitle": "Filtered to your book" + }, + "news": [ + { + "time": "14:31", + "source": "AI · Quiver Research", + "headline": "▲ Hyperscaler capex tracker rolls forward — MSFT + META combined FY '25 guide now +34% YoY, reinforcing NVDA/SMCI thesis.", + "impactLabel": "+ Bull", + "impactClass": "pos" + }, + { + "time": "14:18", + "source": "Bloomberg", + "headline": "Tesla cuts Model Y prices in China by 6% as BYD pressure intensifies in Q2 sales window.", + "impactLabel": "High risk", + "impactClass": "high" + }, + { + "time": "13:54", + "source": "Reuters", + "headline": "FOMC minutes signal one more cut probable in Sept — yields fall 9 bps on the long end.", + "impactLabel": "+ Bull", + "impactClass": "pos" + }, + { + "time": "13:30", + "source": "AI · Earnings whisper", + "headline": "CRWD whisper revised −2.1% after channel checks show enterprise renewal slippage.", + "impactLabel": "Watch", + "impactClass": "med" + }, + { + "time": "12:48", + "source": "CNBC", + "headline": "Apple confirms Vision Pro 2 component orders for Q4 — 1.4M unit run rate implied.", + "impactLabel": "+ Bull", + "impactClass": "pos" + }, + { + "time": "11:22", + "source": "AI · Anomaly detector", + "headline": "▲ SMCI dark-pool prints up 220% vs 30-day baseline — likely block accumulation.", + "impactLabel": "Watch", + "impactClass": "med" + } + ], + + "footer": { + "live": "Live data — refresh on demand", + "source": "Quotes via IEX Cloud · Robinhood + IBKR linked", + "version": "Quiver Live · v1.4.0" + } +} diff --git a/skills/live-artifact/examples/stock-portfolio-live/provenance.json b/skills/live-artifact/examples/stock-portfolio-live/provenance.json new file mode 100644 index 000000000..8521f72cc --- /dev/null +++ b/skills/live-artifact/examples/stock-portfolio-live/provenance.json @@ -0,0 +1,19 @@ +{ + "generatedAt": "2026-05-06T19:32:18.000Z", + "generatedBy": "agent", + "notes": "Personal trading portfolio dashboard — total value, today's P&L, featured ticker chart, holdings table, watchlist, news/signals, and an AI-generated investment recommendation. Designed to be refreshable on demand: price/holdings/news fields can be rewritten in data.json without re-authoring the template. All numbers, percentages, and SVG polyline points are pre-formatted into display-ready strings so the template stays free of formatting/expression logic; direction is encoded via an explicit dirClass field. External links (exchange, Yahoo, TradingView, per-row exchange page) are bound as plain attribute interpolations — no inline scripting. Refresh runner should write a fresh data.json that matches the template's binding shape; daemon validates against bounded JSON limits and re-derives index.html from template.html + data.json. No credentials, OAuth tokens, cookies, raw provider responses, or HTTP envelopes are persisted; forbidden keys (raw, payload, body, headers, cookie, authorization, token, secret, credential, password) are not present in data.json.", + "sources": [ + { + "label": "Brokerage holdings (mocked)", + "type": "connector" + }, + { + "label": "Quotes (mocked)", + "type": "connector" + }, + { + "label": "AI recommendation", + "type": "derived" + } + ] +} diff --git a/skills/live-artifact/examples/stock-portfolio-live/template.html b/skills/live-artifact/examples/stock-portfolio-live/template.html new file mode 100644 index 000000000..55ff92932 --- /dev/null +++ b/skills/live-artifact/examples/stock-portfolio-live/template.html @@ -0,0 +1,1071 @@ + + + + + +{{data.summary.brand}} · Live Portfolio + + + + + + + +
+
+ +
{{data.summary.brand}}
+
{{data.summary.pro}}
+
+
+ + + {{data.summary.marketState}} + + Last sync + {{data.summary.lastSync}} +
+
+ +
+ + +
+
+
+
+
{{data.summary.totalLabel}}
+
{{data.summary.accountsLabel}}
+
+
+ +
{{data.summary.totalValue}}
+ +
+ + {{data.summary.todayArrow}} + {{data.summary.todayPnl}} + today · {{data.summary.todayPct}} + + + {{data.summary.allTimeArrow}} + {{data.summary.allTimePnl}} + all time · {{data.summary.allTimePct}} + + + {{data.summary.alphaLabel}} + {{data.summary.alpha}} + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+ {{data.summary.axisTicks.0.label}} + {{data.summary.axisTicks.1.label}} + {{data.summary.axisTicks.2.label}} + {{data.summary.axisTicks.3.label}} + {{data.summary.axisTicks.4.label}} + {{data.summary.axisTicks.5.label}} + {{data.summary.axisTicks.6.label}} +
+
+
+ +
+
+
{{data.kpis.0.label}}
+
{{data.kpis.0.value}}
+
{{data.kpis.0.sub}}
+
+
+
{{data.kpis.1.label}}
+
{{data.kpis.1.value}}
+
{{data.kpis.1.sub}}
+
+
+
{{data.kpis.2.label}}
+
{{data.kpis.2.value}}
+
{{data.kpis.2.sub}}
+
+
+
{{data.kpis.3.label}}
+
{{data.kpis.3.value}}
+
{{data.kpis.3.sub}}
+
+
+
+ + +
+ +
+
+
+
{{data.featured.iconText}}
+
+
+ {{data.featured.symbol}} + {{data.featured.exchangeName}} ↗ + Yahoo ↗ + TradingView ↗ +
+
{{data.featured.name}} · {{data.featured.sector}}
+
+
+
+
{{data.featured.price}}
+
{{data.featured.delta}}
+
+
+ +
+
+
{{data.featured.stats.0.label}}
+
{{data.featured.stats.0.value}}
+
+
+
{{data.featured.stats.1.label}}
+
{{data.featured.stats.1.value}}
+
+
+
{{data.featured.stats.2.label}}
+
{{data.featured.stats.2.value}}
+
+
+
{{data.featured.stats.3.label}}
+
{{data.featured.stats.3.value}}
+
+
+
{{data.featured.stats.4.label}}
+
{{data.featured.stats.4.value}}
+
+
+
{{data.featured.stats.5.label}}
+
{{data.featured.stats.5.value}}
+
+
+ +
+
+ + + + + + + + + + + + + + + + {{data.featured.priceLabelText}} + +
+
+
+ +
+
+
{{data.ai.tag}}
+
+ {{data.ai.headlineAccent}} {{data.ai.headlineRest}} +
+
{{data.ai.sub}}
+
+ +
+
+
Conviction
+
{{data.ai.conviction}}
+
+
+
+
{{data.ai.targetLabel}}
+
{{data.ai.target}}
+
{{data.ai.upside}} upside
+
+
+
Suggested size
+
{{data.ai.size}}
+
{{data.ai.sizeSub}}
+
+
+ +
+

Why now · catalysts

+
+
+
{{data.ai.catalysts.0.n}}
+
{{data.ai.catalysts.0.title}} — {{data.ai.catalysts.0.text}}
+
+
+
{{data.ai.catalysts.1.n}}
+
{{data.ai.catalysts.1.title}} — {{data.ai.catalysts.1.text}}
+
+
+
{{data.ai.catalysts.2.n}}
+
{{data.ai.catalysts.2.title}} — {{data.ai.catalysts.2.text}}
+
+
+
+
+ +
+ + +
+
+
{{data.holdingsPanel.title}}
+
{{data.holdingsPanel.subtitle}}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SymbolSharesAvg costLastMarket valueUnrealized P&LAllocation
+
+ {{data.holdings.0.icon}} + + {{data.holdings.0.symbol}} + {{data.holdings.0.exchName}} ↗ + + {{data.holdings.0.companyName}} + +
+
{{data.holdings.0.shares}}{{data.holdings.0.avgCost}}{{data.holdings.0.last}}{{data.holdings.0.marketValue}} + + {{data.holdings.0.pnl}} + {{data.holdings.0.pnlPct}} + + + + {{data.holdings.0.alloc}} +
+
+ {{data.holdings.1.icon}} + + {{data.holdings.1.symbol}} + {{data.holdings.1.exchName}} ↗ + + {{data.holdings.1.companyName}} + +
+
{{data.holdings.1.shares}}{{data.holdings.1.avgCost}}{{data.holdings.1.last}}{{data.holdings.1.marketValue}} + + {{data.holdings.1.pnl}} + {{data.holdings.1.pnlPct}} + + + + {{data.holdings.1.alloc}} +
+
+ {{data.holdings.2.icon}} + + {{data.holdings.2.symbol}} + {{data.holdings.2.exchName}} ↗ + + {{data.holdings.2.companyName}} + +
+
{{data.holdings.2.shares}}{{data.holdings.2.avgCost}}{{data.holdings.2.last}}{{data.holdings.2.marketValue}} + + {{data.holdings.2.pnl}} + {{data.holdings.2.pnlPct}} + + + + {{data.holdings.2.alloc}} +
+
+ {{data.holdings.3.icon}} + + {{data.holdings.3.symbol}} + {{data.holdings.3.exchName}} ↗ + + {{data.holdings.3.companyName}} + +
+
{{data.holdings.3.shares}}{{data.holdings.3.avgCost}}{{data.holdings.3.last}}{{data.holdings.3.marketValue}} + + {{data.holdings.3.pnl}} + {{data.holdings.3.pnlPct}} + + + + {{data.holdings.3.alloc}} +
+
+ {{data.holdings.4.icon}} + + {{data.holdings.4.symbol}} + {{data.holdings.4.exchName}} ↗ + + {{data.holdings.4.companyName}} + +
+
{{data.holdings.4.shares}}{{data.holdings.4.avgCost}}{{data.holdings.4.last}}{{data.holdings.4.marketValue}} + + {{data.holdings.4.pnl}} + {{data.holdings.4.pnlPct}} + + + + {{data.holdings.4.alloc}} +
+
+ {{data.holdings.5.icon}} + + {{data.holdings.5.symbol}} + {{data.holdings.5.exchName}} ↗ + + {{data.holdings.5.companyName}} + +
+
{{data.holdings.5.shares}}{{data.holdings.5.avgCost}}{{data.holdings.5.last}}{{data.holdings.5.marketValue}} + + {{data.holdings.5.pnl}} + {{data.holdings.5.pnlPct}} + + + + {{data.holdings.5.alloc}} +
+
+ {{data.holdings.6.icon}} + + {{data.holdings.6.symbol}} + {{data.holdings.6.exchName}} ↗ + + {{data.holdings.6.companyName}} + +
+
{{data.holdings.6.shares}}{{data.holdings.6.avgCost}}{{data.holdings.6.last}}{{data.holdings.6.marketValue}} + + {{data.holdings.6.pnl}} + {{data.holdings.6.pnlPct}} + + + + {{data.holdings.6.alloc}} +
+
+ {{data.holdings.7.icon}} + + {{data.holdings.7.symbol}} + {{data.holdings.7.exchName}} ↗ + + {{data.holdings.7.companyName}} + +
+
{{data.holdings.7.shares}}{{data.holdings.7.avgCost}}{{data.holdings.7.last}}{{data.holdings.7.marketValue}} + + {{data.holdings.7.pnl}} + {{data.holdings.7.pnlPct}} + + + + {{data.holdings.7.alloc}} +
+
+ {{data.holdings.8.icon}} + + {{data.holdings.8.symbol}} + {{data.holdings.8.exchName}} ↗ + + {{data.holdings.8.companyName}} + +
+
{{data.holdings.8.shares}}{{data.holdings.8.avgCost}}{{data.holdings.8.last}}{{data.holdings.8.marketValue}} + + {{data.holdings.8.pnl}} + {{data.holdings.8.pnlPct}} + + + + {{data.holdings.8.alloc}} +
+
+ + +
+ +
+
+
{{data.watchlistPanel.title}}
+
{{data.watchlistPanel.subtitle}}
+
+
+
+
+
+
{{data.watchlist.0.symbol}}
+
{{data.watchlist.0.companyName}}
+
+ {{data.watchlist.0.pct}} +
+
{{data.watchlist.0.price}}
+
+ + + +
+
+ Vol {{data.watchlist.0.volume}} + {{data.watchlist.0.exchName}} ↗ +
+
+
+
+
+
{{data.watchlist.1.symbol}}
+
{{data.watchlist.1.companyName}}
+
+ {{data.watchlist.1.pct}} +
+
{{data.watchlist.1.price}}
+
+ + + +
+
+ Vol {{data.watchlist.1.volume}} + {{data.watchlist.1.exchName}} ↗ +
+
+
+
+
+
{{data.watchlist.2.symbol}}
+
{{data.watchlist.2.companyName}}
+
+ {{data.watchlist.2.pct}} +
+
{{data.watchlist.2.price}}
+
+ + + +
+
+ Vol {{data.watchlist.2.volume}} + {{data.watchlist.2.exchName}} ↗ +
+
+
+
+
+
{{data.watchlist.3.symbol}}
+
{{data.watchlist.3.companyName}}
+
+ {{data.watchlist.3.pct}} +
+
{{data.watchlist.3.price}}
+
+ + + +
+
+ Vol {{data.watchlist.3.volume}} + {{data.watchlist.3.exchName}} ↗ +
+
+
+
+
+
{{data.watchlist.4.symbol}}
+
{{data.watchlist.4.companyName}}
+
+ {{data.watchlist.4.pct}} +
+
{{data.watchlist.4.price}}
+
+ + + +
+
+ Vol {{data.watchlist.4.volume}} + {{data.watchlist.4.exchName}} ↗ +
+
+
+
+
+
{{data.watchlist.5.symbol}}
+
{{data.watchlist.5.companyName}}
+
+ {{data.watchlist.5.pct}} +
+
{{data.watchlist.5.price}}
+
+ + + +
+
+ Vol {{data.watchlist.5.volume}} + {{data.watchlist.5.exchName}} ↗ +
+
+
+
+
+
{{data.watchlist.6.symbol}}
+
{{data.watchlist.6.companyName}}
+
+ {{data.watchlist.6.pct}} +
+
{{data.watchlist.6.price}}
+
+ + + +
+
+ Vol {{data.watchlist.6.volume}} + {{data.watchlist.6.exchName}} ↗ +
+
+
+
+
+
{{data.watchlist.7.symbol}}
+
{{data.watchlist.7.companyName}}
+
+ {{data.watchlist.7.pct}} +
+
{{data.watchlist.7.price}}
+
+ + + +
+
+ Vol {{data.watchlist.7.volume}} + {{data.watchlist.7.exchName}} ↗ +
+
+
+
+ +
+
+
{{data.newsPanel.title}}
+
{{data.newsPanel.subtitle}}
+
+
+
+
{{data.news.0.time}}
+
+
{{data.news.0.source}}
+
{{data.news.0.headline}}
+
+ {{data.news.0.impactLabel}} +
+
+
{{data.news.1.time}}
+
+
{{data.news.1.source}}
+
{{data.news.1.headline}}
+
+ {{data.news.1.impactLabel}} +
+
+
{{data.news.2.time}}
+
+
{{data.news.2.source}}
+
{{data.news.2.headline}}
+
+ {{data.news.2.impactLabel}} +
+
+
{{data.news.3.time}}
+
+
{{data.news.3.source}}
+
{{data.news.3.headline}}
+
+ {{data.news.3.impactLabel}} +
+
+
{{data.news.4.time}}
+
+
{{data.news.4.source}}
+
{{data.news.4.headline}}
+
+ {{data.news.4.impactLabel}} +
+
+
{{data.news.5.time}}
+
+
{{data.news.5.source}}
+
{{data.news.5.headline}}
+
+ {{data.news.5.impactLabel}} +
+
+
+ +
+ +
+ + + + +