* add social-media-matrix-tracker template skill
Package a new template-mode live-artifacts skill for a cinematic social media matrix dashboard, including a default example and reusable template seed.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(skill): unblock CI for social-media-matrix-tracker-template
- Add skill id to DE/FR/RU EN-fallback skill lists in content.{ts,fr.ts,ru.ts}
to satisfy localized-content.test.ts coverage assertion (Validate workspace CI).
- Expand SKILL.md triggers with social analytics / content performance / TikTok /
Instagram / YouTube + 中文 关键词 (新媒体 / 数据看板 / 抖音 / 小红书) per
reviewer feedback (lefarcen P2#7).
Addresses merge-blocking CI failure flagged by @mrcfps and the discoverability
gap flagged by @lefarcen. Other code-level findings (chart precision, drag
overlay, tooltip clipping, touch input) will be addressed in follow-up commits.
* fix(i18n): add Indonesian translations for Cloudflare Pages deploy keys
Adds 18 fileViewer.* keys for Cloudflare Pages deploy support that were
introduced on main but never localized to id.ts, blocking typecheck for
any branch synced with main.
Translations cover: deploy provider selection (Vercel/Cloudflare Pages),
Cloudflare API token + Account ID inputs, generic provider deploy/error
messaging.
* fix(skill): address all P2 reviewer findings on social-media-matrix-tracker-template
Code fixes (template.html):
- ROI chart redraw now respects per-chart decimals from chartState (was hard-coded 0, lost roiChart precision after first interaction)
- Drag overlay state now cleared BEFORE redraw on mouseup, and on mouseleave during drag (was leaving stale overlay until next interaction)
- mixChart hover now updates insights focus panel with stack breakdown (was tooltip-only)
- sentimentChart hover now updates insights focus panel with sentiment label + share (was tooltip-only)
- Tooltip now measures itself + clamps inside viewport with edge-flip (was clipping at right/bottom edges)
- Added touchstart/move/end/cancel handlers + keyboard arrow-key navigation + Escape (was mouse-only, unusable on touch devices and keyboards)
- drawLineChart guards 0/1-element datasets (renders 'No data' or single labelled dot instead of NaN axis labels / Infinity min-max)
Docs (SKILL.md):
- Workflow now explicitly mentions tooltip clamping, panel update on every chart type, and touch/keyboard a11y
- Added 'Adapting the sample data safely' section with array shape, unit, decimals, and KPI lock-step contract
- Output contract now spells out artifact wrapper requirements, identifier convention, no external CDN/fonts, and single-document rule (no parallel index.html)
Addresses lefarcen P2 #1-5, P2 #7-10, and P3 #6.
* fix(skill): mrcfps round-2 review — fix touch dispatch + sync example.html
mrcfps Looper bot 0.6.2 caught two regressions in the round-1 fix push:
1. Touch dispatch threw TypeError: the previous adapter built a real
Event then Object.assign-ed clientX/clientY/target onto it. Event.target
is a read-only getter, so the assignment threw before the synthetic
mousedown ever reached the line chart. Touch support was effectively
broken on real devices.
Replaced with plain pointer objects passed directly into dedicated
handleTouchStart / handleTouchMove / handleTouchEnd handlers (which
reuse nearestByMouse, redraw, updateInsights, etc). No more synthetic
event dispatch, no read-only field assignment.
2. example.html was untouched in round 1, so the showcase that users
open directly still had x+14/y+14 tooltip clipping, no touch support,
no decimals state, no overlay-clearing fix, no insights update on
mix/sentiment hover, no short-series guard — all the things the
reusable template was just fixed for.
example.html is identical to template.html except for the JS block
(verified via diff of body + data calls), so we copy template.html
over example.html so the two stay in lock-step. Future template fixes
should mirror by 'cp assets/template.html example.html' until we
adopt build-time generation.
---------
Co-authored-by: tuolaji <tuola@tuolajideMacBook-Air.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tuola-waj <tuola@nexu.io>
The saved-key badge was wired to `isSavedState = apiKeyConfigured && !hasPendingEdit`,
which made it disappear on the first keystroke as soon as the user
started typing a draft replacement. Users reading the settings panel
saw the saved key indicator vanish before they had clicked Save and
reasonably assumed the stored credential had already been overwritten
or removed. Credential editing is a high-trust workflow; a UI that
fakes a state change before the durable write is the wrong default.
Replaced the boolean derivation with a single helper
`deriveComposioCredentialState` returning one of `empty | pending-new |
saved | saved-pending`. The component now shows the saved-key badge
for both `saved` and `saved-pending`, so the indicator stays anchored
while the user types. The hint text differentiates all four states so
the unsaved-replacement case is still clearly called out.
Helper is exported and unit-tested in
`apps/web/tests/components/SettingsDialog.test.ts` against the
empty, pending-new, saved, and saved-pending states plus the
whitespace-only-draft edge case that should still resolve to
`saved`.
Co-authored-by: Nagendhra <nagendhra405@gmail.com>
The Indonesian locale was added in fcc37c6 (#fcc37c6) right around
the same time the Cloudflare Pages artifact deployment feature
landed (09eb88f). The Cloudflare PR added 18 new keys to `Dict`
and translated them in every existing locale, but `id.ts` was
populated against the previous shape — so once both PRs sat on
main, every workspace `tsc -b --noEmit` failed with:
src/i18n/locales/id.ts(3,14): error TS2740: Type ... is missing
the following properties from type 'Dict':
'fileViewer.deployProviderLabel', 'fileViewer.vercelProvider',
'fileViewer.cloudflarePagesProvider',
'fileViewer.deployToProvider', and 14 more.
That turns CI red on every open PR until someone adds the missing
keys. The same race will happen the next time a feature lands new
strings unless the locale defaults to English fallbacks the way
de / es-ES / fr / hu / ja / ko / pl / tr already do.
Apply the `import { en } from './en'; ...en, ...overrides` pattern
to id.ts. Existing Indonesian translations stay as overrides — no
regression on what the user already sees in Indonesian — and any
new key landed by a future feature inherits an English fallback
automatically. Translators can replace fallbacks one key at a
time without breaking the build in between.
Verified locally:
- web `tsc -b --noEmit` clean (was failing on `id.ts(3,14)`)
- `tsx scripts/i18n-check.ts` passes
* feat(contracts): types for folder-import endpoint
Add ImportFolderRequest, ImportFolderResponse to the public contract
surface. Extend ProjectMetadata with a baseDir field — when set, the
project's files live at this absolute path instead of .od/projects/<id>/.
Stored as the realpath() result so symlinks cannot redirect later writes.
Refs nexu-io/open-design#597
* feat(daemon): support metadata.baseDir for folder-rooted projects
Add resolveProjectDir() and metadata-aware variants of listFiles,
readProjectFile, writeProjectFile, ensureProject so a project's files
can live under metadata.baseDir (the user's chosen folder) instead of
.od/projects/<id>/. metadata.baseDir is opt-in — projects without it
keep the existing .od/projects/<id>/ behavior unchanged.
When listFiles walks a baseDir-rooted project, it skips conventional
build / install dirs (node_modules, .git, dist, build, .next, .nuxt,
.turbo, .cache, .output, out, coverage, __pycache__, .venv, vendor,
target, .od, .tmp) so the file panel stays focused on design content
instead of being dominated by lockfiles and node_modules.
Add detectEntryFile() — best-effort lookup for index.html or any
.html at the folder root, used by the import endpoint to seed the
initial active tab.
Refs nexu-io/open-design#597
* feat(daemon): add POST /api/import/folder endpoint
Creates a project rooted at the submitted local folder. metadata.baseDir
points at that folder and OD reads / writes there directly — no copy,
no shadow tree, mirroring how Cursor / Claude Code / Aider behave. The
user owns the workspace and is responsible for their own version
control.
Safety:
- baseDir is canonicalized via fs.promises.realpath() at import time so
user-controlled symlinks can't redirect later writes. resolveSafe
enforces the bounds check against the literal stored path; without
realpath, a symlink (e.g. ~/sneaky → /etc) would let writeProjectFile
escape the project tree at every later call because the OS follows
the symlink at open() time.
- Post-realpath lstat ensures the canonical target is itself a real
directory (defense-in-depth).
- The data directory (RUNTIME_DATA_DIR) and its descendants are
refused after symlink resolution so a redirect into the daemon's
own state can't masquerade as a project import.
The web client wires this through state/projects.ts → App.tsx,
landing the user on the auto-detected entry file when present.
Refs nexu-io/open-design#597
* feat(desktop): expose native folder picker to renderer
Adds an Electron preload script that exposes window.electronAPI.pickFolder
via contextBridge. Wires dialog.showOpenDialog through ipcMain so the
web UI can open a native folder selector for project import. Browser-only
users fall back to a text input for the absolute path (handled in the
web layer); the picker stays an optional convenience on the desktop
binary.
ipcMain.handle() registers handlers in an internal map that is not
exposed via eventNames(), so the natural-looking guard
if (!ipcMain.eventNames().includes('dialog:pick-folder')) ipcMain.handle(...)
is always true. On a second createDesktopRuntime() call (dev hot-reload,
packaged-vs-electron mode swap) the body re-runs and ipcMain.handle()
throws 'Attempted to register a second handler'. Use removeHandler()
+ handle() unconditionally — removeHandler() is a documented no-op
when nothing is registered, making the pair idempotent.
Includes *.cts in the apps/desktop tsconfig so the preload script is
typechecked.
Refs nexu-io/open-design#597
* feat(web): add 'From existing folder' option to New Project
UI surface for the import flow:
- A new 'Open folder' affordance in NewProjectPanel that uses the
native picker on Electron (window.electronAPI.pickFolder) and falls
back to an absolute-path text input in the browser.
- importFolderProject() in state/projects.ts: typed wrapper around
POST /api/import/folder using @open-design/contracts types.
- App.tsx wires the response: prepend the new project to the list,
navigate to it, and select the auto-detected entry file as the
active tab.
Skill / design-system pickers from the existing prototype tab are
reused — folder import is a project-creation flow, not a separate
project type.
Refs nexu-io/open-design#597
* docs(architecture): document folder-import endpoint
Adds POST /api/import/folder to the daemon API table and a 'Folder
import' section explaining the single-mode design (direct read/write
in metadata.baseDir, mirroring Cursor / Claude Code / Aider), the
realpath() canonicalization, the RUNTIME_DATA_DIR refusal, and the
SKIP_DIRS list applied to listFiles for baseDir-rooted projects.
Refs nexu-io/open-design#597
* test(daemon): unit + integration tests for folder import
Two new files:
apps/daemon/tests/folder-import-projects.test.ts (13 unit tests):
- resolveProjectDir behavior under all metadata combinations,
including the fallback when baseDir is relative and the
isSafeId-bypass when baseDir is set
- detectEntryFile: index.html priority, .html fallback, null when
no html, no descent into subdirs
- listFiles with metadata.baseDir: walk, SKIP_DIRS hides node_modules
/ .git / dist, back-compat for projects without baseDir
apps/daemon/tests/folder-import-route.test.ts (10 integration tests):
- Happy path: baseDir stored in metadata, importedFrom='folder',
conversation created, entry file detected
- Error paths: missing baseDir, empty, relative, non-existent,
pointing at a file
- Security: realpath canonicalization (the symlink test was the one
that surfaced the original /var vs /private/var mismatch in
RUNTIME_DATA_DIR comparison on macOS)
- Security: a symlink that resolves into RUNTIME_DATA_DIR is rejected
after realpath, not before
Refs nexu-io/open-design#597
* fix(daemon): wire baseDir metadata into chat + deploy reads
Two bugs caught in Codex automated review of #624:
1. chat-route was passing the metadata object directly as the listFiles
opts argument: `listFiles(PROJECTS_DIR, projectId, chatMeta)`. The
listFiles contract reads opts.metadata, not opts itself, so this
silently fell back to .od/projects/<id>/ instead of the imported
folder. existingProjectFiles was empty for baseDir-rooted projects.
Wrap as `{ metadata: chatMeta }`.
2. deploy.ts read project files via readProjectFile without the
metadata third argument, so for baseDir-rooted projects the deploy
and preflight endpoints would look in .od/projects/<id>/ and fail
with file-not-found instead of reading the imported folder. Thread
options.metadata through buildDeployFilePlan → readProjectFile and
pass project?.metadata at the two server.ts callsites
(`POST /api/projects/:id/deploy` and the preflight endpoint).
Add a regression test that locks the listFiles contract: passing a
bare metadata object as opts must NOT scan baseDir — it must fall back
to the standard project dir, otherwise callers can leak the wrong
folder by mistake.
Refs nexu-io/open-design#597, #624 (Codex review)
* fix(daemon): ensure correct metadata handling in folder import
Addressed issues with metadata handling in folder import functionality. Updated the listFiles and readProjectFile methods to correctly utilize the metadata.baseDir, ensuring that project files are read from the intended directory. Added regression tests to verify that passing a bare metadata object does not inadvertently scan the baseDir, maintaining the integrity of project file access.
Refs nexu-io/open-design#597
* fix(daemon): security hardening from Codex review of #624
P1 findings from automated review:
1. POST /api/projects + PATCH /api/projects/:id rejected
client-supplied metadata.baseDir. baseDir is privileged: it lets a
project root inside the user's filesystem, and the realpath() +
RUNTIME_DATA_DIR reentry checks live only on /api/import/folder.
Allowing it on the generic create/patch path lets an attacker
smuggle e.g. /etc through and bypass every import-time guard.
Both endpoints now refuse a baseDir field with 400.
2. resolveSafeReal() helper: realpath()s each candidate path (or its
longest existing prefix for write paths) and re-validates against
realpath(projectRoot). The original resolveSafe() only did a
string-prefix check, which was fooled by symlinks *inside* a
baseDir-rooted project. A repo containing 'assets -> /Users/me/.ssh'
passed the literal prefix check but readFile() followed the link
at open() time. resolveSafeReal() is now used by readProjectFile,
writeProjectFile, and deleteProjectFile.
3. Multer chat-upload destination now resolves to metadata.baseDir for
imported folder projects via a module-level lookup wired to db at
startServer() boot. Previously attachments landed in
.od/projects/<id>/ even for baseDir projects, so the agent (which
runs with cwd=baseDir) couldn't open them.
P2 findings:
4. searchProjectFiles threads metadata through listFiles +
resolveProjectDir so /api/projects/:id/search hits the right tree.
5. buildProjectArchive + buildBatchArchive now accept metadata so
'Download .zip' works for imported folder projects.
6. Watcher subscribe() resolves to baseDir for imported projects so
live-reload SSE actually fires when the user edits files in their
own folder. Registry stays keyed by the canonical directory.
7. Template snapshotting reads source-project files with metadata
so a template can be saved from a baseDir-rooted source.
Tests:
- Regression: POST /api/projects with metadata.baseDir → 400.
- Regression: descendant symlink (assets/leak.txt -> /etc/hosts) is
refused on the raw read endpoint.
Refs nexu-io/open-design#597, #624 (Codex P1+P2 review)
* fix(daemon): close two regressions found in #624 review round 2
@mrcfps caught two more correctness gaps:
1. Archive root symlink escape — buildProjectArchive accepts an optional
?root=<subdir> param to scope the zip to a subdirectory. The path was
resolved with the string-only resolveSafe(), so a directory symlink
inside an imported folder (docs -> /Users/me/.ssh) passed the prefix
check and collectArchiveEntries() then walked outside the project
tree. Switch to the symlink-aware resolveSafeReal() — the same one
that already protects raw read/write/delete paths. The walker itself
already skips dirent symlinks via !isDirectory && !isFile, so
canonicalizing the root is the only missing piece.
2. PATCH metadata wiped baseDir — updateProject() replaces metadata
wholesale. The previous guard only blocked an explicit baseDir
change, but a normal patch that *omits* baseDir (a UI editing
linkedDirs only sends { metadata: { kind, linkedDirs } }) silently
detached imported projects from their folder root. Subsequent
reads/writes/watch/deploy fell back to .od/projects/<id>.
Re-stamp the immutable folder-import fields (baseDir, importedFrom='folder')
from the existing project record onto the incoming patch when the
project is imported. A patch that supplies a *different* baseDir
still gets rejected as before; a patch that supplies the *same*
baseDir is accepted as a no-op. A patch on a non-imported project
that tries to set baseDir is also still rejected (preserves the
POST /api/projects guard from the previous round).
Tests:
- archive endpoint: ?root=<symlink-to-/etc> → 400.
- patch endpoint: PATCH that omits baseDir on an imported project keeps
baseDir intact (project still resolves to the user's folder after).
Refs nexu-io/open-design#597, #624 (Codex P1 round 2)
* fix(web): add Indonesian deploy provider copy
---------
Co-authored-by: INFINITY <valentyn.sotov@trendarena.app>
Co-authored-by: Siri-Ray <2667192167@qq.com>
* feat(prompt-templates): add Notion-style team dashboard (Live Artifact)
Adds a single image prompt template under the Live Artifact category — a
Notion-native team dashboard mockup with KPI grid, 7-day sparkline,
activity feed, and linked-database task table.
This is the first prompt template to use the curated Live Artifact
category, whose de/fr/ru localization slots were already reserved in
apps/web/src/i18n/content{,.fr,.ru}.ts. Only the new tag 'live-artifact'
is added to each locale's PROMPT_TEMPLATE_TAGS map (+1 line each) so the
arrayContaining check in e2e/tests/localized-content.test.ts continues
to pass.
Template-level only: no new surface, no loader changes, no schema or
TypeScript type changes.
* fix(prompt-templates,i18n): register 'Live Artifact' category and template ID fallback for de/fr/ru
CI's e2e/localized-content.test.ts enumerates LOCALIZED_CONTENT_IDS from
apps/web/src/i18n/content.ts and asserts:
- ids.promptTemplates === sorted(all template ids in prompt-templates/)
- ids.promptTemplateCategories ⊇ all categories actually used by templates
- ids.promptTemplateTags ⊇ all tags actually used by templates
The new notion-team-dashboard-live-artifact template introduced both
the first 'Live Artifact' category and the first prompt-template id
without a copy translation, so each locale needs:
- 'Live Artifact' added to *_PROMPT_TEMPLATE_CATEGORIES (currently
consumed via arrayContaining; order doesn't matter)
- 'notion-team-dashboard-live-artifact' listed in
*_PROMPT_TEMPLATE_IDS_WITH_EN_FALLBACK so it joins ids.prommplates
via the EN-fallback path (no per-locale title/summary copy needed)
The 'live-artifact' tag was already added to *_PROMPT_TEMPLATE_TAGS in
the previous commit on this branch.
3 files changed, +6 / -3.
* fix(i18n): register Live Artifact category + template id fallback (CI repair on #799)
* fix(i18n): register Live Artifact category + template id fallback (CI repair on #799)
* fix(i18n): register Live Artifact category + template id fallback (CI repair on #799)
* fix(prompt-templates): scrub live/connector affordances from notion-team-dashboard prompt (#799 review)
Reviewers (mrcfps, lefarcen) flagged that even with the amber "Sample
data — design preview" banner and the "(sample data)" footer, the inner
prompt blob still asked the model to render UI affordances claiming a
real Notion / Composio connector binding ("Live · synced" pill, "Last
refreshed just now", "Refresh from Notion" blue button, callout saying
numbers are "pulled from your {workspace} Notion workspace via the
Composio connector"). That contradicts the prompt-only contract and
reintroduces the #778 mock-honesty concern.
Rewrite the prompt blob so every UI element is consistently presented
as seeded sample data:
- topbar.preview_pill (was live_pill):ample · design preview' pill
with explicit negative instruction no to render any live/sync pill
- page_header.meta_row: drop 'Last refreshed', 'Auto' toggle, and the
'Refresh from Notion' blue button; explicit DO NOT instructions
- callout: 'prompt-only design preview ... seeded sample data ... not
pulled from a real Notion workspace and not refreshed via the
Composio connector. For real refreshable / connector-backed Live
Artifacts, use the live-artifact skill.' Also removes the bare
'{workspace}' placeholder that was not using {argument ...} syntax
(P2 nit from lefarcen).
- activity_feed_card.subtitle: 'Notion-style seeded activity for
design preview' (was 'From Notion')
- linked_database.title/subtitle: marked '(sample)' / 'seeded sample
rows · no live connector binding'
- linked_database.row_styles: explicit negative instruction not to
render an 'Updated ↻' refresh badge
- footer: 'Notion-style sample data · seeded design preview, not bound
to any Notion workspace or Composio connector'
- honesty_rule: enumerates all live/sync/refresh affordanche
generator must NOT render
Top-level metadata (id, title, summary, category, tags, model, aspect,
previewImageUrl, source) is unchanged. Preview PNG already shows the
amber banner and a layout without a Refresh button, so it matches the
new in-prompt language.
* fix(prompt-templates): scrub live/connector affordances from notion-team-dashboard prompt (#799 review)
Reviewers (mrcfps, lefarcen) flagged that even with the amber "Sample data — design preview" banner and the "(sample data)" footer, the inner prompt blob still asked the model to render UI affordances claiming a real Notion / Composio connector binding ("Live · synced" pill, "Last refreshed just now", "Refresh from Notion" blue button, callout saying numbers are "pulled from your {workspace} Notion workspace via the Composio connector"). That contradicts the prompt-only contract and reintroduces the #778 mock-honesty concern.
Rewrite the prompt blob so every UI element is consistently presented as seeded sample data:
- topbar.preview_pill (was live_pill): 'Sample · design preview' pill with explicit negative instruction not to render any live/sync pill.
- page_header.meta_row: drop 'Last refreshed', 'Auto' toggle, and the 'Refresh from Notion' blue button; explicit DO NOT instructions for all three.
- callout: 'prompt-only design preview ... seeded sample data ... not pulled from a real Notion workspace and not refreshed via the Composio connector. For real refreshable / connector-backed Live Artifacts, use the live-artifact skill.' Also removes the bare '{workspace}' placeholder that was not using {argument ...} syntax (P2 nit from lefarcen).
- activity_feed_card.subtitle: 'Notion-style seeded activity for design preview' (was 'From Notion').
- linked_database.title/subtitle: marked '(sample)' / 'seeded sample rows · no live connector binding'.
- linked_database.row_styles: explicit negative instruction not to render an 'Updated ↻' refresh badge.
- footer: 'Notion-style sample data · seeded design preview, not bound to any Notion workspace or Composio connector'.
- honesty_rule: enumerates all live/sync/refresh affordances the generator must NOT render.
Top-level metadata (id, title, summary, category, tags, model, aspect, previewImageUrl, source) is unchanged. Preview PNG already shows the amber banner and a layout without a Refresh button, so it matches the new in-prompt language.
* fix(prompt-templates): scrub live/connector affordances from notion-team-dashboard prompt (#799 review)
Reviewers (mrcfps, lefarcen) flagged that even with the amber "Sample data — design preview" banner and the "(sample data)" footer, the inner prompt blob still asked the model to render UI affordances claiming a real Notion / Composio connector binding ("Live · synced" pill, "Last refreshed just now", "Refresh from Notion" blue button, callout saying numbers are "pulled from your {workspace} Notion workspace via the Composio connector"). That contradicts the prompt-only contract and reintroduces the #778 mock-honesty concern.
Rewrite the prompt blob so every UI element is consistently presented as seeded sample data:
- topbar.preview_pill (was live_pill): 'Sample · design preview' pill with explicit negative instruction not to render any live/sync pill.
- page_header.meta_row: drop 'Last refreshed', 'Auto' toggle, and the 'Refresh from Notion' blue button; explicit DO NOT instructions for all three.
- callout: 'prompt-only design preview ... seeded sample data ... not pulled from a real Notion workspace and not refreshed via the Composio connector. For real refreshable / connector-backed Live Artifacts, use the live-artifact skill.' Also removes the bare '{workspace}' placeholder that was not using {argument ...} syntax (P2 nit from lefarcen).
- activity_feed_card.subtitle: 'Notion-style seeded activity for design preview' (was 'From Notion').
- linked_database.title/subtitle: marked '(sample)' / 'seeded sample rows · no live connector binding'.
- linked_database.row_styles: explicit negative instruction not to render an 'Updated ↻' refresh badge.
- footer: 'Notion-style sample data · seeded design preview, not bound to any Notion workspace or Composio connector'.
- honesty_rule: enumerates all live/sync/refresh affordances the generator must NOT render.
Top-level metadata (id, title, summary, category, tags, model, aspect, previewImageUrl, source) is unchanged. Preview PNG already shows the amber banner and a layout without a Refresh button, so it matches the new in-prompt language.
---------
Co-authored-by: joeylee12629-star <joeylee12629-star@users.noreply.github.com>
* add flowai live dashboard template skill
Introduce a new template-mode skill under the live-artifacts scenario with a default interactive example and seed template so users can generate polished, refresh-ready team dashboards quickly.
Co-authored-by: Cursor <cursoragent@cursor.com>
* add preview screenshot for flowai live dashboard template
Attach the provided dashboard screenshot under docs/screenshots/skills so the template contribution includes a visual preview artifact.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(flowai-template): reposition as static prototype dashboard skill
Address review feedback on PR #801:
- SKILL.md: drop `scenario: live-artifacts` and live-related triggers;
align with peer single-page dashboard skills using
`mode: prototype` + `scenario: operations` so the four-file
live-artifact contract no longer applies.
- references/checklist.md: rewrite quality gates around the static
prototype scope (export-from-DOM, responsive breakpoints, theme-aware
charts).
- assets/template.html:
- CSV export now reads every visible row from the table DOM,
including the Workflow column, instead of a hardcoded fixture.
- Add 1300px and 720px breakpoints; the main grid stacks to one
column, stat cards fall back to two then one, tabs wrap, table
scrolls horizontally on phones.
- Move chart colors into CSS variables (--chart-stroke,
--chart-fill, --chart-axis, --chart-bar-label, --chart-bar-value)
so dark-mode toggling re-derives them; chart canvases are
re-rendered after theme switch.
- Hash-sync tabs (#members | #details | #activity), animate the
role bar chart only on first reveal of the details tab,
fall back when CanvasRenderingContext2D.roundRect is unavailable,
add Esc to exit zoom and prevent tooltip clipping.
- example.html: title cleanup to match new skill identity.
Localized content:
- Add `flowai-live-dashboard-template` to DE/FR/RU
SKILL_IDS_WITH_EN_FALLBACK lists in apps/web/src/i18n so the
e2e localized-content test passes.
---------
Co-authored-by: tuolaji <tuola@tuolajideMacBook-Air.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tuola Ge <gexingli@refly.ai>
* add live-dashboard skill
Notion-style team dashboard rendered as a Live Artifact.
Wires the OD 0.4.0 connector catalog (#381) end-to-end:
refresh-on-open, manual Refresh tween, auto-refresh, stale state.
Falls back to seeded mock data when no connector is bound.
* address PR #778 review comments
P1 — security and correctness:
- skills/live-dashboard/assets/template.html · skills/live-dashboard/example.html: escape every connector-derived string before innerHTML interpolation. Adds a tiny e() helper and routes feed.who/action/target/suffix/icon, row.title/icon/due/prio, person.name/color/id, KPI label/delta through it. Closes lefarcen #3200122795 + #3200122820.
- skills/live-dashboard/SKILL.md (live behavior section): align connector poll URL with references/connectors.md — POST /api/od/connectors/poll with { project, read } body, not /api/od/connectors/<id>/poll. Closes codex bot #3200100897.
- apps/web/src/i18n/content{,.ru,.fr}.ts: register live-dashboard in DE_/RU_/FR_SKILL_IDS_WITH_EN_FALLBACK so the localized-content e2e check passes. Closes mrcfps #3200122059.
- skills/live-dashboard/references/connectors.md: prepend a Status callout that names skills/live-artifact/ as the canonical file/CLI live-artifact contract and frames the HTTP shape as a forward-looking proposal sitting alongside it (out-of-the-box the artifact runs on seeded data; only seedNextChange() needs swapping when POST /api/od/connectors/poll lands). Closes lefarcen #3200122811.
P2 — quality and honesty:
- skills/live-dashboard/references/connectors.md: rewrite the auth_ref resolution step to match apps/daemon/src/media-config.ts (OD_MEDIA_CONFIG_DIR → OD_DATA_DIR → <projectRoot>/.od/media-config.json, $HOME/~/relative paths handled via expandHomePrefix). Closes codex bot #3200100906.
- skills/live-dashboard/example.html: switch the live-pill to a sticky Sample data state with a grey static dot, rewrite the callout to admit the figures are seeded fixtures, retitle the toast and the refresh tooltip, and refuse to flip to Live · synced inside updateTimes(). Adds a .pill-live.sample CSS variant. Closes lefarcen #3200122823.
- skills/live-dashboard/assets/template.html: hoist <meta name=od:project> from <body> into <head>. Closes lefarcen #3200122832.
- skills/live-dashboard/assets/template.html · example.html: add role=button + tabindex=0 + aria-current to every clickable .ws / .side-search / .nav-item, and wire a single document-level keydown handler that maps Enter/Space to a synthetic click for any role=button div (skipping real buttons / anchors / form controls). Closes lefarcen #3200122837.
- skills/live-dashboard/assets/template.html: implement the KPI tween + flash + snapshotKpi() the SKILL.md prose already promised — first render builds escaped cards, subsequent renderKpi(prev) calls tween numeric values and flash() the cells that actually changed; refresh() now calls snapshotKpi() before mutating state and forwards prev. SKILL.md spells out the wire-up. Closes lefarcen #3200122839.
* gate KPI tween + flash + row/feed highlight on prefers-reduced-motion
Addresses mrcfps's non-blocking review item on PR #778 (comment #3200614137,
template.html:453). The CSS @media (prefers-reduced-motion: reduce) block
already neutralizes CSS animations and transitions, but the new JS-driven
helpers kept moving for opted-out users:
- tweenText() scheduled requestAnimationFrame updates for 600ms
- flash() toggled the .flash highlight class for 700ms
- renderFeed()/renderRows() applied .feed-row.new / .db-row.changed
classes which carry transient backgrounds even when their CSS
animations are off
Both runtimes (assets/template.html and example.html) now share a
reduceMotion() helper (window.matchMedia('(prefers-reduced-motion:
reduce)').matches). When it returns true:
- tweenText()/tween() set the final value immediately and return
- flash() returns without touching the class
- renderFeed()/renderRows() pass null as the highlight id so the .new /
.changed classes are never applied
Normal-motion users see the existing tween + flash + highlight pulse
unchanged. Keeps the P0 prefers-reduced-motion row in
references/checklist.md honest for agents that copy this template
verbatim.
---------
Co-authored-by: joey <joey@joeydeMacBook-Air.local>
Co-authored-by: joeylee12629-star <joeylee12629-star@users.noreply.github.com>
* add waitlist-page skill
* fix(waitlist-page): address PR review feedback
- Remove novalidate from example.html form
- Ensure checkValidity() guard present in both template and example
- Remove required from firstname input in template
- Add token escaping rules to SKILL.md workflow (step 9)
- Add token mapping/fallback rules for BORDER/SUCCESS/STRIPE/DECO (step 7)
- Fix mobile quality gate to be measurable (375x667, 390x844)
- Promote hardcoded #fff, rgba(0,0,0,0.9), rgba(255,255,255,0.9) to
CSS variables (--btn-label, --ticker-bg, --ticker-fg) in template
- Create references/checklist.md with P0/P1/P2 tiers; countdown timer
is now a hard P0 prohibition; a11y gate split into six specific checks"
* fix: resolve P0 color and accessibility issues
- Add role=status to success messages for screen reader announcement
- Replace all hardcoded hex/rgba colors with template tokens
- Update SKILL.md with comprehensive color token mapping rules
- SVG decorations now use CSS variables instead of hardcoded strokes
* fix: address PR review feedback on scope, scrolling, and font tokens
Fixes:
- Restore pricing-page files accidentally deleted in previous commit
(skills/pricing-page/SKILL.md and example.html now back on branch)
- Remove temp-original.html scratch file from commit
- Fix mobile viewport scrolling: change 'height: 100vh; overflow: hidden'
to 'min-height: 100svh; overflow-x: hidden; overflow-y: auto'
so content doesn't clip on 375×667 and 390×844 screens
- Split font tokens into URL-safe and CSS-safe variants:
* {{DISPLAY_FONT_URL}} and {{DISPLAY_FONT_CSS}} for display fonts
* {{BODY_FONT_URL}} and {{BODY_FONT_CSS}} for body fonts
This fixes encoding: spaces as '+' in Google Fonts URL, literal in CSS
- Update SKILL.md frontmatter with new font input fields
- Update token escaping rules to document the split
* fix: resolve token contract mismatch and remove hardcoded colors from example.html
P0 Fixes:
- Remove all hardcoded colors from example.html (except #2D6A4F for --success)
- Use CSS variables for all color values: --btn-label, --ticker-bg, --ticker-fg, --deco-stroke
- Fix gradient to use var(--deco) instead of hardcoded #D1632B
- Apply consistent color expressions across decorations and text
Token Contract Fixes:
- template.html now uses full CSS expressions for opacity-based colors:
* {{BORDER_EXPRESSION}} instead of {{BORDER_HEX}} (no # prefix)
* {{BTN_LABEL_EXPRESSION}} instead of {{BTN_LABEL_HEX}}
* {{TICKER_BG_EXPRESSION}}, {{TICKER_FG_EXPRESSION}}, {{DECO_STROKE_EXPRESSION}}
- Remove extra quotes from font tokens in template:
* --font-body: {{BODY_FONT_CSS}} instead of '{{BODY_FONT_CSS}}'
* Font tokens are already quoted if needed, no wrapping
- Update SKILL.md frontmatter with all color expression inputs and descriptions
- Update token mapping rules to clarify the new contract:
* Hex tokens: simple six-digit colors
* Expression tokens: full CSS values (rgba/color-mix), no # prefix
* Font tokens: CSS font-family values, no extra wrapping
- Update token escaping rules to reflect new contract
This ensures agents can follow SKILL.md instructions without producing invalid CSS.
* fix: remove final hardcoded colors from example.html - P0 complete
- Button text: #fff → var(--btn-label)
- Ticker background: rgba(0,0,0,0.9) → var(--ticker-bg)
- Ticker text: rgba(255,255,255,0.9) → var(--ticker-fg)
- Logo text: fill=white → fill=var(--btn-label)
All colors now derive from design system tokens. Only #2D6A4F (--success) allowed hardcoded exception.
* fix: correct --btn-label contrast for CTA readability
Change --btn-label from #1A1410 (same as button background) to #FDE8DF
(light background color) so button text has proper contrast against
the dark --accent button background.
This resolves the black-text-on-black issue that broke the main
email capture action and satisfies the checklist button contrast gate.
* fix: add visible focus indicator for input accessibility
P1 Accessibility Polish:
- Update .form-row input:focus to include outline and outline-offset
- Before: border-color only, removing default outline (no visible focus)
- After: border-color + 2px outline + 2px offset (clear focus indicator)
This satisfies the checklist P1 focus-style gate and ensures keyboard
users can see which form field has focus. Both example.html and
template.html updated so agents copy complete focus patterns.
* fix: remove hardcoded logo shadow color - P0 compliance
- Add --logo-shadow CSS variable derived from foreground
- example.html: box-shadow 0 2px 8px rgba(0,0,0,0.08) → var(--logo-shadow)
- template.html: add {{LOGO_SHADOW_EXPRESSION}} placeholder
- Update SKILL.md with logo_shadow_expression input and mapping rules
All colors in example.html now derive from design system tokens.
Ensures agents copy compliant reference without hardcoded shadow colors.
* fix: register waitlist-page skill in i18n localized content registry
Add waitlist-page to locale-specific skill fallback lists so the web
content coverage test passes when the new skill is discovered:
- apps/web/src/i18n/content.ts: Add to DE_SKILL_IDS_WITH_EN_FALLBACK
- apps/web/src/i18n/content.fr.ts: Add to FR_SKILL_IDS_WITH_EN_FALLBACK
- apps/web/src/i18n/content.ru.ts: Add to RU_SKILL_IDS_WITH_EN_FALLBACK
The skill falls back to English localization for now; localized
descriptions can be added to each locale file later.
Fixes: web content coverage test now passes (6/6 tests).
* fix: wire template and checklist into skill workflow as mandatory gates
Restructure waitlist-page SKILL.md workflow to enforce the hardened
template-based execution path:
- Add Preflight section: agents MUST read assets/template.html first
- Add explicit token mapping and escaping rules (steps 2-4)
- Add mandatory Validation section: run references/checklist.md P0/P1
gates BEFORE emitting artifact; fail fast if any P0 gate fails
- Update Quality gates section to emphasize template-based execution
and reinforce P0/P1 gate hierarchy
- Update Output section: only emit after P0 passes; re-validate on
iterations
This prevents agents from writing HTML from scratch or skipping the
hardened seed (template) and validation (checklist) that this PR adds.
* refactor(waitlist-page): replace literal logo placeholder with token
- Replaced `[LOGO]` with `{{LOGO_MARK}}` in template.html
- Added `logo_mark` to inputs in SKILL.md
- Updated mapping rules in SKILL.md to handle raw SVG or text for logo
- Updated P0 validation gates in SKILL.md and checklist.md to ensure logo replacement
* fix(waitlist-page): enforce strict escaping and sanitization for logo token
- Mandate HTML-escaping for text initials.
- Enforce strict allowlist-based sanitization for inline SVG (stripping `<script>`, `on*`, `<foreignObject>`, `href`, `xlink:href`, `url()`).
- Add fallback to escaped text initials for invalid/unsafe SVG.
* docs(waitlist-page): sync logo_mark frontmatter description with rules
- Updated the `logo_mark` input description in the SKILL.md frontmatter to explicitly outline the new requirements for HTML-escaped text or strict allowlist-sanitized SVG.
* fix(waitlist-page): add logo_fg_expression to guarantee contrast in logo mark text fallback
- Added `--logo-fg` CSS variable mapped to `{{LOGO_FG_EXPRESSION}}`.
- Updated `.logo-container` in `template.html` to inherit typography styles and apply `--logo-fg` for safe fallback when rendering escaped initials.
- Enforced WCAG AA contrast for logo initials against container background in `checklist.md`.
* refactor(waitlist-page): migrate hex color tokens to full css expressions
* refactor(waitlist-page): strict validation for color expression tokens to prevent CSS injection
* docs(waitlist-page): update validation summary to reflect strict color grammar
---------
Co-authored-by: Siri-Ray <2667192167@qq.com>
* Optimize Windows packaged web output
* Fix packaged contracts runtime build
* Optimize Windows packaged size pruning
* Prune Windows root Next payload
* Remove Windows bundled Node runtime
* Prune Windows standalone duplicate Next
* Add tools-pack cache foundation
* Cache Windows packaged build layers
* Cache Windows workspace builds
* Cache Electron-ready Windows app
* Split Windows tools-pack module
* Cache Windows dir build outputs
* Split Windows pack build modules
* Document Windows NSIS smoke namespace limits
* Move Windows NSIS smoke note to agents guide
* Optimize Windows beta packaging
* Bump packaged beta base version
* Improve Windows installer namespace UX
* Improve Windows tools-pack cache keys
* Stabilize Windows beta cache version keys
* Cache Windows workspace build outputs
* Optimize windows release beta cache layers
* Cache windows release dependencies
* Trim windows release cache before save
* Refresh windows tools-pack cache key
* Improve windows installer preflight prompts
* Fallback NSIS installer strings to English
* Fix Windows installer cleanup and preflight
* Improve Windows NSIS state logging
* Fix system NSIS Persian language alias
* Use long-path removal for Windows uninstall
* Fix mac tools-pack tests on Windows
* Address Windows packaging review feedback
* Fix Windows installer cache namespace isolation
* Include web output mode in Windows tarball cache key
* Use unique Windows release cache save keys
Add a one-shot OD_LEGACY_DATA_DIR migrator so packaged Desktop users can recover 0.3.x repo .od data into the 0.4.x data root. The migrator stages payloads before promotion, refuses unsafe merges and symlinks, rolls back failed promotion or marker writes, and extends packaged daemon startup handling for long migrations while failing fast on daemon exits.
Closes#710
* Support overriding the Codex executable path
* Replace save-as-template prompts with an in-app dialog
* Seed local packaged app config from workspace
* Fix packaged config and connection test overrides
* Keep tools-pack mac config seeding self-contained
* Require absolute CODEX_BIN overrides
* docs(readme): add @nexudotio X link + Stay in the loop section
- Add X/Twitter follow badge in the header next to Discord, so visitors
can subscribe to release notes and milestone threads in one click.
- Add a short "Stay in the loop" section above "Star us" pointing to
@nexudotio on X. Discord is for chat, X is for the public milestones
(releases, new skills, new design systems).
No other changes.
* feat(web): add Follow @nexudotio pill to entry sidebar foot
- Surface the official X account next to the Settings/Pet/Language pills
so users can subscribe to release notes from inside the app, not only
the README.
- Uses the existing .foot-pill style (already supports <a>, has
text-decoration: none) and the existing 'external-link' Icon — no new
CSS, no new icons, no i18n key required (single short English label).
- Opens in a new tab with rel="noreferrer noopener".
Pairs with the README badge added in this same PR.
---------
Co-authored-by: Tuola-waj <ge@nexu.io>