mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
0fe5ee66edb9f9a97419e194e9cd4563964f484b
197
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0fe5ee66ed |
Add French Quickstart (#397)
* Add French Quickstart * Avoid hard-coded Design System count in French Quickstart * Refresh French Quickstart skills overview * Clarify French Quickstart live catalogs * Broaden French Quickstart skill result wording |
||
|
|
01fbe2681a |
Add Ukrainian localization (#395)
* feat(i18n): add Ukrainian localization * feat(i18n): register Ukrainian locale * docs(uk): fix section order, expand repo tree, align details with README.md * table and links fix in README.uk.md * translation polishing #1 * typo fix #1 * translation polishing #2 |
||
|
|
10e8e2d38d |
feat(i18n): add Russian gallery metadata (#396)
Co-authored-by: DrMaks22 <191687254+DrMaks22@users.noreply.github.com> |
||
|
|
179b23d7e7 |
Modernize multi-provider API proxy routing
Adds provider-specific API proxy routes for Anthropic, OpenAI-compatible, Azure OpenAI, and Google Gemini; normalizes provider SSE streams; updates web provider clients/settings/docs/tests; and forwards Gemini token limits. |
||
|
|
2618e5239d |
Add French locale and README (#326)
* Add French locale and README * Polish French UI copy * Place French after German in README switchers |
||
|
|
64609e6a2a |
Refactor RUNTIME_DATA_DIR resolution logic (#391)
* Refactor RUNTIME_DATA_DIR resolution logic Set OD_DATA_DIR=$HOME/.open-design #390 * Refactor resolveDataDir to accept projectRoot parameter Add validation if the resolved path is not writable. |
||
|
|
ce4eb92aa2 |
fix(desktop): show window on macOS dock activate (#270)
* fix(desktop): show window on macOS dock activate
On macOS, closing the Electron window via the red close button hides it
rather than destroying it. Without an app.on('activate') handler, clicking
the dock icon afterward does nothing — the window stays hidden indefinitely.
Add app.on('activate') in runDesktopMain that calls desktop.show(), and
add the corresponding show() method to DesktopRuntime / createDesktopRuntime
that calls window.show() + window.focus() when the window is not destroyed.
* fix(desktop): hide window on macOS close instead of destroying it
On macOS the red X should hide the window, not destroy it. Without this,
clicking close triggers window-all-closed → shutdown, so the activate
handler added in the previous commit can never show anything.
Intercept the close event on darwin and call event.preventDefault() +
window.hide() when the runtime has not been asked to stop. The stopped
flag is set to true before window.close() is called in the programmatic
shutdown path, so that path is unaffected.
---------
Co-authored-by: StotheC90 <StotheC90@users.noreply.github.com>
|
||
|
|
a719f02aa2 |
fix(web): normalize daemon proxy origins
Fix web sidecar proxy requests so same-origin browser requests reach the daemon without tripping origin validation, while unrelated origins remain rejected. Fixes #388. |
||
|
|
8381f4607c |
feat(i18n): refresh Russian locale and add README (#393)
Co-authored-by: DrMaks22 <191687254+DrMaks22@users.noreply.github.com> |
||
|
|
bc957aab6d |
fix(web): URL-load HTML preview iframes by default; ?forceInline=1 opt-out (#384)
Part 1 of 2 for #368. The FileViewer's HTML preview previously fetched the file plus every relative <link>/<script> and concatenated the bytes back into a srcDoc string. That model breaks down for multi-file artifacts: one bad fetch dark-frames the whole iframe, every script shows up as "Inline Babel script (n)" with no source map, and the asset tree gets re-fetched from scratch on every Open click. Switch the default HTML preview to <iframe src=...> against the daemon's raw-file endpoint — the same shape the existing Preview pane already uses. The browser handles its own asset resolution, source maps work, DevTools shows real filenames, and per-asset HTTP caching applies. Decks and comment-mode keep the existing srcDoc path so the deck and comment postMessage bridges (which must run before user scripts) stay intact. Non-deck HTML that depends on the in-iframe localStorage shim can opt back into the inline path with `?forceInline=1` on the host page. The decision logic is extracted to file-viewer-render-mode.ts so it's testable without a DOM harness; both helpers are covered by unit tests and the FileViewer integration test asserts the URL-load vs srcDoc markup for plain HTML, declared decks, and deck-shaped HTML. Refs #368 Co-authored-by: DevForgeAI CI/CD Engineer <devforge-ai@development.ai> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
02638af353 |
Add linux x64 AppImage to tools-pack and release workflows (#369)
* feat(tools-pack): extend config types for linux platform
* feat(tools-pack): add linux resource files (icon, .desktop template)
* feat(tools-pack): export linuxResources paths
* feat(tools-pack): scaffold linux.ts module
* chore(tools-pack): add vitest devdep for linux lane unit tests
* feat(tools-pack): add buildDockerArgs helper for containerized linux builds
* chore: update pnpm lockfile after adding vitest dep
* feat(tools-pack): add renderDesktopTemplate helper
* fix(tools-pack): use @@ICON_PATH@@ token in linux .desktop template
Reviewer flagged the third .replace() in renderDesktopTemplate as dead code
because the template hardcoded Icon=open-design-@@NAMESPACE@@ instead of
using @@ICON_PATH@@. Switch the template to @@ICON_PATH@@ so install logic
controls the icon stem name independent of namespace, and move the
sanitizeNamespace assertion out of the renderDesktopTemplate describe block
into its own describe.
* feat(tools-pack): add matchesAppImageProcess helper
* test(tools-pack): cover matchesAppImageProcess missing APPIMAGE env case
Closes a coverage gap flagged by code review: a process whose executable
matches /tmp/.mount_*/AppRun but has no APPIMAGE env should be rejected.
The implementation already returned false for this case (undefined ===
installPath is false); this test pins the behavior explicitly.
* feat(tools-pack): implement packLinux native build path
* fix(tools-pack): packLinux extra resources, output pre-clear, publish never
Code review flagged three plan-level omissions in packLinux that mac.ts
handles correctly:
1. writeAssembledApp now writes packagedConfigPath (open-design-config.json)
with namespace, nodeCommandRelative, and namespaceBaseRoot. Without it
apps/packaged falls back to defaults at runtime and cannot find the
namespace runtime tree.
2. writeLinuxBuilderConfig now bundles the resource tree and packaged
config into the AppImage via extraResources. Without it the running
app cannot find skills/, design-systems/, craft/, frames/, or the
bundled bin/node.
3. runElectronBuilderLinux now pre-clears appBuilderOutputRoot and passes
--publish never to electron-builder, preventing stale-artifact bleed
between runs and accidental publish attempts in CI when env tokens
are present.
Also aligns appId with mac/win (io.open-design.desktop) and drops a
no-op productNameSafe template-literal.
* feat(tools-pack): implement containerized linux build via Docker
* feat(tools-pack): register linux CLI commands
* fix(tools-pack): align linux electron-builder config with mac.ts
Smoke testing the AppImage revealed the daemon sidecar was missing from
the bundled app.asar:
Cannot find module '@open-design/daemon/dist/sidecar/index.js'
Root cause: writeLinuxBuilderConfig was missing the 'files' field, so
electron-builder used defaults that excluded transitive workspace deps
from the asar. Plus several other mac.ts patterns that I dropped from
the plan: artifactName, executableName, extraMetadata.main/name/
productName/version, npmRebuild=false, nodeGypRebuild=false,
buildDependenciesFromSource=false, compression=maximum, top-level icon.
Switch asar:true → asar:false to match mac.ts (easier to debug missing
files; perf difference negligible for dev installs).
* feat(tools-pack): implement linux install
* feat(tools-pack): implement linux start with extract-and-run
The packaged sidecar's 35-second wait timeout is exceeded when the
AppImage runs from a FUSE-mounted SquashFS (Node module loads + daemon
init are slow through FUSE). Pass --appimage-extract-and-run as the
first arg so AppImage extracts to /tmp first; subsequent file reads
go through a real filesystem and daemon boot completes in time.
Wait for apps/packaged to write desktop-root.json (60s ceiling, generous
to cover AppImage extraction overhead), then fetch desktop status via
sidecar IPC, return the merged LinuxStartResult.
* fix(tools-pack): align linux start helper with mac.ts (log echo + write semantics)
Code review flagged two unjustified divergences from mac.ts in
startPackedLinuxApp:
1. Missing OD_DESKTOP_LOG_ECHO=0 in spawn extraEnv. Without it the
packaged logger echoes to the spawned process's stdout, which goes
nowhere (logFd: null). Added the suppression to match mac.ts.
2. The desktop log truncate writeFile() was wrapped in .catch(() =>
undefined), silently swallowing fs errors that would later surface as
confusing missing-log symptoms. Removed the .catch so errors
propagate per mac.ts.
Also added an inline comment explaining the 60s waitForMarker timeout
(vs mac's tighter ceiling) so the rationale is preserved at the
call site.
* feat(tools-pack): implement linux stop with marker validation
* fix(tools-pack): align linux stop with mac.ts (graceful shutdown + reason strings)
Code review flagged divergences from mac.ts in stopPackedLinuxApp:
1. No graceful IPC SHUTDOWN attempt before SIGTERM/SIGKILL. Mac's
pattern lets Electron renderers + sidecars flush state (SQLite WAL,
logs) first. `gracefulRequested: true` was hardcoded, lying to
callers about what actually happened. Now attempts SHUTDOWN with a
1500ms timeout and reports the actual outcome.
2. The dead-PID-but-marker-exists branch returned reason 'ok' (the
neutral placeholder from readDesktopRootIdentityMarker), which says
nothing useful. Override to 'marker-pid-not-running' to match mac.ts.
3. After a clean stop, remove the desktop-root.json marker so a
subsequent start has a fresh slate (mac.ts does this too).
* fix(tools-pack): clear stale desktop-root.json before linux start
Smoke-testing the install/start/stop loop revealed waitForMarker
returns instantly when a stale marker from a previous run still exists
on disk (e.g., the previous AppImage was killed without going through
'tools-pack linux stop'). The start function then reports success
without actually waiting for the new spawn to write its own marker.
Defensively remove the marker file before spawning. mac.ts removes it
in stop, so a clean stop->start sequence has nothing to remove here.
This only matters for crash-recovery.
* fix(tools-pack): linux stop validates extract-and-run AppImages
Smoke testing exposed a gap from Task 7: matchesAppImageProcess only
recognized FUSE-mode (/tmp/.mount_*/AppRun) but Task 13 launches with
--appimage-extract-and-run, which puts the live executable at
/tmp/appimage_extracted_<hex>/<binary>. Stop's cmdOk validation
returned false, marker validation failed, the running app was
classified as 'unmanaged' and stop refused to kill it.
Fix:
1. matchesAppImageProcess accepts both runner patterns. Extract-and-run
regex matches /^\/tmp\/appimage_extracted_[^/]+\/[^/]+$/.
2. stopPackedLinuxApp now passes paths.installAppImagePath (or the
built fallback) as the canonical install path, not marker.appPath
(which apps/packaged unhelpfully writes as '/' on Linux).
3. linux.test.ts gains 2 new tests covering the extract-and-run mode
(both positive and the wrong-APPIMAGE-env negative case).
* fix(tools-pack): resolve linux paths in stop (typecheck regression from previous commit)
* feat(tools-pack): implement linux logs
* feat(tools-pack): implement linux uninstall
* feat(tools-pack): implement linux cleanup
* docs(tools-pack): document linux lane in READMEs and AGENTS files
* ci(release): add linux x64 AppImage to release-beta and release-stable
Mirrors the existing build_mac/build_win pattern with a build_linux job
in both release workflows. Builds via `tools-pack linux build
--containerized --to appimage` so the AppImage is linked against the
electronuserland/builder glibc 2.27 baseline (portable across distros)
rather than the ubuntu-latest glibc 2.39.
The linux asset is uploaded to the immutable version release tag
alongside mac/win. The beta channel-feed release (latest-mac.yml,
latest.yml) is intentionally not extended with latest-linux.yml because
tools/pack/src/linux.ts has no electron-builder publish block wired,
so the auto-update feed would point users at a feed that never updates.
AppImage auto-update is a separate follow-on.
Linux is unsigned (no signing path in tools-pack yet), so the beta
asset uses the .unsigned suffix matching the windows convention; the
stable asset uses no suffix, matching the stable windows convention.
* fix(tools-pack): propagate --dir/--portable into containerized linux build
The inner `pnpm tools-pack linux build` invocation in `buildDockerArgs`
only forwarded `--to` and `--namespace`. Callers passing `--dir` (e.g.
the new release workflows using `--dir $RUNNER_TEMP/tools-pack`) had
their flag silently dropped: the container defaulted to writing under
/project/.tmp/tools-pack while the host's `findBuiltAppImage` looked at
the caller's chosen `--dir`, producing "expected AppImage not found"
on any non-default tool-pack root. Callers passing `--portable` had
the same drop, baking build-machine runtime roots into shipped artifacts.
Fix:
- Mount `${config.roots.toolPackRoot}:/tools-pack` (new third volume,
alongside the existing /project, /home/builder, and cache mounts).
- Forward `--dir /tools-pack` to the inner build so its output lands
inside the mounted host dir.
- Forward `--portable` when `config.portable` is true.
The mount overlaps harmlessly with /project when toolPackRoot lives
under workspaceRoot (default case): Docker exposes the same host inode
at both paths. The existing .docker-home and .docker-cache/* mounts
continue to shadow the parent at their specific /home/builder paths.
Document the shell-interpolation safety invariant on the inner command:
config.namespace is sanitized at config-time, config.to is enum-validated,
config.portable is boolean -- none can carry shell metacharacters.
Tests: add coverage for the new /tools-pack mount, --dir forwarding,
and --portable propagation (both true and false branches).
Resolves the P1 review feedback from the Codex bot on PR #369.
* docs(tools-pack): polish linux README based on PR review
Addresses non-blocking P2/P3 review feedback on PR #369:
- AppImage launch mode: name the test distros (Ubuntu 24.04, Arch Linux)
and frame the FUSE-vs-extract-and-run gap as an order-of-magnitude
improvement instead of an unspecified slowdown.
- Optional system tools: add a libfuse2 paragraph distinguishing FUSE
launch (needs libfuse2) from extract-and-run (does not), with the
Ubuntu-24-vs-pre-24 package name caveat.
- New section "Format choice: why AppImage first" anchoring the
AppImage-only decision against industry precedent (VS Code, Discord,
Slack, Cursor, Obsidian) so the rationale survives without a reviewer.
- Out of scope: convert the dense one-liner into a bulleted list, mark
AppImage signing as gated on GPG infra + verification flow design
(no ETA), explain the latest-linux.yml gap, and remove the now-stale
"release lane" entry since this PR adds it.
* fix(tools-pack): add --appimage-extract-and-run to installed .desktop launcher
The XDG .desktop file installed by `tools-pack linux install` invoked
the AppImage directly via `Exec=env OD_NAMESPACE=<ns> <exec> %U`. That
bypassed the extract-and-run flag that `tools-pack linux start` applies,
so menu launches and `od://` desktop activations could hit the FUSE
slow path that was already shown to make the daemon sidecar exceed
apps/packaged's 35-second startup timeout. CLI-spawned starts succeeded
while menu-launched starts could fail with the same artifact.
Add `--appimage-extract-and-run` to the template's `Exec=` line and
update the renderDesktopTemplate test expectation. New regression test
locks the flag into place so a future template edit can't silently
drop it.
Resolves a P1 review finding from mrcfps/Looper on PR #369.
* fix(tools-pack): treat signal-terminated container builds as failures
`runBuildInContainer` resolved the build promise on `code === null`,
which in Node's child-process `exit` event means the child was
terminated by a signal (SIGTERM, SIGKILL, OOM-killer, parent process
death). A killed Docker build could therefore make `packLinux` report
a containerized build as complete even though the artifact was
partial or missing.
Accept the `signal` argument on the exit handler. Resolve only when
`code === 0 && signal == null`. Otherwise reject with a message
naming either the non-zero code or the terminating signal so the
failure mode is visible in CI logs and `tools-pack linux build --json`
output.
Resolves a P1 review finding from mrcfps/Looper on PR #369.
* fix(tools-pack): tear down orphaned process tree on failed linux start
If `startPackedLinuxApp` spawned the AppImage but the post-spawn
readiness path then failed -- either because the 60s waitForMarker
ceiling elapsed without the daemon writing desktop-root.json, or
because fetchDesktopStatus threw -- the detached child was left
running. Because the marker is the only persistent identity source
used by `stopPackedLinuxApp`, future lifecycle commands could not
associate the orphan with the namespace, leaving stale Electron and
sidecar processes plus stale IPC sockets that would interfere with
subsequent starts.
Wrap the readiness wait + status fetch in try/catch. On failure,
collect the spawned child's process tree via listProcessSnapshots +
collectProcessTreePids and stopProcesses() it (the same path
stopPackedLinuxApp uses for its tree teardown), then rethrow the
original error. Cleanup errors are swallowed so the original failure
is preserved in the rejection.
Extract the tree-teardown helper as `teardownOrphanedStart` so the
intent is documented at the call site without inlining 4 imports of
implementation detail.
Resolves a P2 review finding from mrcfps/Looper on PR #369.
* fix(tools-pack): use `corepack pnpm` in containerized linux build
The inner command in `buildDockerArgs` started with `corepack enable`,
which writes pnpm/yarn/npm shims into the directory containing the
node binary. In `electronuserland/builder:base`, that directory is
owned by root, but the container runs as the host's non-root uid via
`--user` (so build artifacts come out owned by the caller, not root).
The `corepack enable` step therefore fails with EACCES before
`pnpm install` ever runs, blocking the new release `build_linux` job
from publishing the Linux AppImage.
Switch to `corepack pnpm install --frozen-lockfile && corepack pnpm
tools-pack linux build ...`, which resolves and runs the version of
pnpm pinned in package.json's `packageManager` field directly. No
shims, no global mutation, no root writes — corepack just dispatches
to the pinned binary as the unprivileged user.
Update the existing inner-command test to match the new corepack
invocation, and add a regression test that asserts the inner command
contains `corepack pnpm` and never `corepack enable` so a future edit
can't reintroduce the root-write requirement.
Resolves a P1 review finding from mrcfps/Looper on PR #369.
* fix(tools-pack): accept menu-launched processes in linux stop/uninstall
stopPackedLinuxApp validated the live process via matchesStampedProcess
against the process command line, requiring a SIDECAR_SOURCES.TOOLS_PACK
stamp. That worked for `tools-pack linux start` (which spawns with
createProcessStampArgs), but rejected menu launches: the installed
.desktop entry only sets OD_NAMESPACE and does not pass stamp args, so
apps/packaged falls back to a SIDECAR_SOURCES.PACKAGED stamp written
into desktop-root.json -- a perfectly valid identity, just not the one
the validator accepted.
Symptoms with the old behavior:
- `tools-pack linux stop` reported `unmanaged` for menu-launched apps
and refused to stop them.
- `tools-pack linux uninstall` would happily remove the AppImage,
.desktop entry, and icon while the packaged app was still running,
breaking handles to the AppImage's mounted/extracted contents.
Switch the validator to read marker.stamp directly (the file content
written by apps/packaged itself, not the process command) and accept
either TOOLS_PACK or PACKAGED. The expected app/mode/namespace/ipc
fields are still required to match. Mirrors the dual-source acceptance
pattern in mac.ts:709-714.
The matchesAppImageProcess (cmdOk) and namespaceRoot checks are
preserved -- the marker still has to point at our AppImage at a path
in our namespace's runtime root.
Drop the now-unused matchesStampedProcess import.
Resolves a P1 review finding from mrcfps/Looper on PR #369.
* fix(tools-pack): per-platform --to help text in CLI
addBuildOptions is shared across mac/win/linux but its --to help text
hard-coded the mac targets (all|app|dmg|zip), so:
- tools-pack linux --help advertised --to all|app|dmg|zip even
though resolveToolPackBuildOutput accepts only all|appimage|dir,
sending users at invalid targets and hiding the AppImage option.
- tools-pack win --help had the same problem (advertised mac
targets while accepting all|dir|nsis with default nsis).
Parameterize addBuildOptions(command, platform) and back it with a
TO_HELP_BY_PLATFORM table that mirrors the resolver's accepted targets
in config.ts. Update the three call sites.
Smoke verified by running --help for each platform:
linux: all|appimage|dir (default: all)
mac: all|app|dmg|zip (default: all)
win: all|dir|nsis (default: nsis)
The misleading "--signed: build a signed/notarized mac artifact" line
on win/linux is left alone -- out of scope for this fix and not part
of the review feedback.
Resolves a P3 review finding from mrcfps/Looper on PR #369.
* fix(tools-pack): use OD_PACKAGED_NAMESPACE in installed .desktop launcher
The installed .desktop entry's Exec= line set OD_NAMESPACE=<ns>, but
apps/packaged/src/config.ts:9 reads namespace overrides from
OD_PACKAGED_NAMESPACE, not OD_NAMESPACE. The env assignment was a
silent no-op for menu launches: the packaged app fell back to whatever
namespace was baked into open-design-config.json at install time,
ignoring the namespace advertised in the .desktop file.
Practical effect: a .desktop launcher created for namespace "foo"
could end up running as the namespace baked into the AppImage's
shipped config (typically "default"), so installs created across
multiple namespaces could collide silently from menu launches. CLI
launches via `tools-pack linux start` were unaffected because they
pass the namespace through createSidecarLaunchEnv which targets the
correct env var.
Switch the template to OD_PACKAGED_NAMESPACE. Update the existing
renderDesktopTemplate test fixture/expectation, and add a regression
test that asserts the Exec= line uses OD_PACKAGED_NAMESPACE and never
the wrong OD_NAMESPACE name.
Resolves a P1 review finding from mrcfps/Looper on PR #369.
* fix(tools-pack): gate linux uninstall + cleanup on stop status
uninstallPackedLinuxApp called stopPackedLinuxApp first, then deleted
the AppImage / .desktop entry / icon unconditionally. cleanupPackedLinux
Namespace did the same with the output and runtime namespace roots.
Both ignored stop.status -- so when stop returned "partial" (some
processes survived SIGTERM->SIGKILL) or "unmanaged" (the running PID
failed marker validation), uninstall would yank the install files out
from under a still-running packaged app, breaking handles to the
mounted/extracted AppImage contents and leaving an orphan with stale
SQLite WAL files / log handles / IPC sockets.
Extract a small `isSafeToRemoveInstallFiles(stop)` helper that returns
true only for "stopped" or "not-running". Both uninstall and cleanup
short-circuit when it returns false:
- uninstall reports "skipped-process-running" for each removal slot
and "skipped" for the post-install hooks. Existing "ok" / "already-
removed" / "ok"|"missing"|"failed" paths are unchanged.
- cleanup leaves both removed* booleans false and adds a new
`skipped: boolean` field set to true. Old consumers that only read
the booleans see the same "nothing was removed" signal they would
have seen for an already-clean namespace; new consumers can
distinguish "nothing to remove" from "refused to remove."
LinuxUninstallResult.removed.{appImage,desktop,icon} now also accepts
"skipped-process-running"; LinuxUninstallResult.postUninstall.* now
also accepts "skipped". LinuxCleanupResult gains the `skipped` field.
Workspace typecheck clean -- the only consumer is the CLI's printJson,
which doesn't constrain the wire shape.
Resolves a P1 review finding from mrcfps/Looper on PR #369.
|
||
|
|
6b012fc83c |
fix(web): lift coming-soon tip above sticky tabs and make it readable in dark theme (#382)
The "Coming soon" pseudo-tip on .viewer-action / .viewer-toggle had two bugs in the file viewer toolbar: 1. Hidden behind .ws-tabs-bar. The parent shell sets opacity: 0.55, which creates a stacking context, so the ::after's z-index was trapped locally and could not rise above the sticky tabs bar (z-index: 4) in the root context. Pin the parent to z-index: 5. 2. Invisible in dark theme. background: var(--text-strong) becomes off-white in dark mode against color: white. Switch to the same fixed dark slate already used by .comment-target-tooltip (rgba(17, 24, 39, 0.95)) so contrast holds in both themes. |
||
|
|
339a57c65c |
fix(security): bind daemon to localhost by default, add origin validation (#365)
* fix(security): bind daemon to localhost by default, add origin validation middleware The daemon's Express server previously defaulted to binding 0.0.0.0, exposing all 30+ API endpoints to the local network with zero authentication. This included endpoints that spawn CLI agents, write project files, and manage API keys. Changes: - Default bind address changed from 0.0.0.0 to 127.0.0.1 in both server.ts and cli.ts. Users who need network access can still set OD_BIND_HOST=0.0.0.0 explicitly. - Added origin validation middleware on /api/* routes: requests with an Origin header are only allowed from localhost/127.0.0.1/[::1] on the daemon's own port. Non-browser clients (no Origin header) are unaffected. - 7 unit tests for the origin validation logic. - Existing tests: 327/327 passing. * fix: address PR review — fail-closed, OD_WEB_PORT, HTTPS, Origin: null Address feedback from @lefarcen, @mrcfps, and Codex review: P1: Fail-closed when resolvedPort is not yet set (returns 403 instead of passing through). P2: Include OD_WEB_PORT in allowed origins for split-port proxy setups (web port ≠ daemon port). Add HTTPS variants. P3: Exempt Origin: null for sandboxed iframe preview fetches (/api/projects/:id/raw/*) so artifact previews keep working. P4: Update help text from 0.0.0.0 to 127.0.0.1. Test coverage expanded to 13 tests including: - OD_WEB_PORT split-port proxy scenario - Origin: null iframe preview - HTTPS origin variants - Fail-closed before port resolution - Cross-origin rejection with OD_WEB_PORT set All 333 tests passing, TypeScript clean. * fix: scope Origin: null bypass to raw-file previews only, support non-loopback bind host Two issues found in second review round: 1. Origin: null was a global bypass for ALL /api routes, allowing sandboxed iframes to reach state-changing endpoints (project create/delete, agent runs). Now only GET requests to /projects/:id/raw/* pass through with Origin: null — the original intent for iframe file previews. POST/DELETE with Origin: null are rejected. 2. Allowed origins only included loopback addresses. When daemon binds to a non-loopback address (--host for Tailscale, LAN, or 0.0.0.0), browser requests from that address would get 403. Now the bound host is included in allowed origins alongside loopback, keeping the documented network-access escape hatch working for browser clients. Added negative tests for Origin: null on POST/DELETE/non-raw GET, and positive tests for non-loopback bind host scenarios. All 339 tests passing. * fix: centralize origin policy, add spritesheet to Origin: null allowlist Addresses @mrcfps's third review round: 1. isLocalSameOrigin() now uses the same policy as the global origin middleware: HTTPS + HTTP, OD_WEB_PORT, and the explicit bind host (OD_BIND_HOST). Previously it only accepted HTTP on loopback hosts, so requests from https://127.0.0.1 or a Tailscale address could pass the global guard but 403 on per-route checks. 2. /api/codex-pets/:id/spritesheet is a read-only route that sets Access-Control-Allow-Origin: null for canvas drawing by sandboxed iframes. Added to the Origin: null allowlist so the middleware doesn't block it before the route handler runs. 3. buildAllowedOrigins() extracted as a closure so both the middleware and isLocalSameOrigin() share identical logic. 340/340 tests passing. * test: remove dead fail-closed test with unused 5th argument The 'fails closed when port is 0' test called request() with an extra argument the helper ignores, then never asserted on res. Real coverage lives in the dedicated 'fail-closed before port resolution' describe block. |
||
|
|
4de93fe35c |
Add OpenAI media OAuth fallback (#377)
Co-authored-by: Fu Yizheng <fyz3120@sina.cn> |
||
|
|
016c08183f | release: Open Design 0.3.0 open-design-v0.3.0 | ||
|
|
d8e632c291 |
Fix macOS entry view drag region (#373)
Co-authored-by: Fu Yizheng <fyz3120@sina.cn> |
||
|
|
3c954ad2b3 |
feat(i18n): add French locale (#376)
Co-authored-by: Rudy Palfray <rpalfray87@gmail.comy> |
||
|
|
e90e2f9e1f |
feat(skills): add taste-skill-derived web prototype and HTML PPT examples (#358)
* feat(skills): add taste-skill-derived web prototype and HTML PPT examples Adds five skills distilling Leonxlnx/taste-skill into the project's SKILL.md + example.html convention so authors can pick a deliverable shape (web prototype / deck) and a design register (editorial / soft / brutalist) without the LLM defaulting to Inter-on-white slop. - web-prototype-taste-editorial: minimalist-skill applied (warm monochrome, Instrument Serif x Inter Tight, hairline bento) - web-prototype-taste-soft: soft-skill applied (silver canvas, ambient mesh, double-bezel cards, button-in-button CTAs, spring motion) - web-prototype-taste-brutalist: brutalist-skill Swiss-print mode (newsprint, Archivo Black, viewport-bleeding numerals, hazard-red, ASCII syntax) - html-ppt-taste-editorial: 10-slide investor memo deck in the editorial register, compatible with the existing html-ppt static-preview pattern - html-ppt-taste-brutalist: 11-slide tactical-telemetry / CRT-terminal incident debrief deck (scanline overlay, hazard-red alerts, audit log) Each SKILL.md cites the upstream taste-skill source file and lists hard rules, banned patterns, required components, and a pre-flight checklist. * fix(i18n): register new taste skills in German content registry Adds the five new taste-derived skills to DE_SKILL_IDS_WITH_EN_FALLBACK so the German display content coverage test sees them. Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code) * fix(skills): close stray </small> in html-ppt-taste-brutalist telemetry cell The paged-engineers tcell's delta span on slide 04 closed with `</small></span>`, leaving the example with invalid HTML that downstream previews/validators can flag. Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code) |
||
|
|
e3b5bf7fbc |
fix(packaged): include nvm/fnm/mise agent CLI bins in packaged PATH (#364)
* fix(packaged): include nvm/fnm/mise agent CLI bins in packaged PATH `resolvePackagedPathEnv` constructs the daemon PATH for packaged builds (DMG/AppImage/NSIS), but it only includes hardcoded locations (.local/bin, homebrew, etc.) and misses npm-global agent CLIs installed under a version manager. Add `collectNvmFnmBins()` to @open-design/platform that dynamically discovers nvm (`~/.nvm/versions/node/*/bin`), fnm, and mise npm-global bin directories. Wire it into `resolvePackagedPathEnv` alongside the existing static candidates. This complements the dev-mode fix in #346 (agents.ts: userToolchainDirs) by covering the packaged launcher path as well. * fix(packaged): move nvm/fnm bin discovery into packaged-runtime sidecars Keep @open-design/platform focused on generic OS/process primitives by moving the version-manager PATH discovery helpers (existingDirsUnder, collectNvmFnmBins) into apps/packaged/src/sidecars.ts where they are actually consumed by resolvePackagedPathEnv. |
||
|
|
2e28cb0bc9 |
fix(web): scroll workspace tabs in place instead of the window (#363)
Merged per maintainer approval. |
||
|
|
051d9b890d | feat(daemon): add Mistral Vibe CLI agent adapter (#354) | ||
|
|
68493b7b72 |
feat(web): task completion sound + browser notification (#359)
Merged per maintainer approval. |
||
|
|
61cdc3fe4b |
fix(daemon): upgrade better-sqlite3 for Node 24 Windows prebuilt support (#357)
Merged per maintainer approval in pr 解决群. |
||
|
|
f33a7ecb0e | docs(readme): refresh contributors wall (#360) | ||
|
|
72edd4fc60 | fix(clis): Address Codex and Gemini CLIs not being detected by looking for agent CLIs in user toolchain paths (#346) | ||
|
|
58bbc979e3 |
fix(daemon): lead copilot spawn with -p - so prompt-via-stdin is consumed (#351)
|
||
|
|
9d700ec74f |
feat(daemon): persist code agent startup (#255)
* feat(daemon): persist code agent startup * fix: complete all suggestions * fix: types for app config * chore: revert local origin * chore: format to single quotes * fix: duplicate headers * fix: isLocalSameOrigin rewriting issue --------- Co-authored-by: mrcfps <mrc@powerformer.com> |
||
|
|
9c71fc20eb |
feat(web): add "Export as Markdown" pass-through download to share menu (#345)
Closes #279. Adds an `Export as Markdown` entry to the HTML / deck artifact share menu in `FileViewer`. The entry downloads the artifact source verbatim with a `.md` extension and `text/markdown; charset=utf-8` Content-Type — no HTML→markdown conversion runs. The file body is byte-for-byte identical to what the Source view shows. The use case (per the issue thread): users want to pipe the artifact's raw source into markdown-aware tooling — LLM context windows, vault apps, etc. — that key off the `.md` extension. A lossy text-extraction conversion (turndown / similar) was the first thing I tried, but it loses the embedded styles + scripts that make the deck reproducible and was rejected by the issue author in favor of pass-through. Keeping the implementation byte-exact also leaves room for a future "Convert to Markdown" entry to live alongside this one without overlap. Changes: - `apps/web/src/runtime/exports.ts` — new `exportAsMd(source, title)` helper; module-level comment updated to include MD in the export family description. - `apps/web/src/components/FileViewer.tsx` — new share menu entry, positioned right after `Export as standalone HTML` and before the `Save as template` divider so the file-format exports stay grouped together. - `apps/web/src/i18n/types.ts` + 14 locale files (`en`, `de`, `ja`, `ko`, `zh-CN`, `zh-TW`, `es-ES`, `fa`, `pt-BR`, `ru`, `ar`, `pl`, `tr`, `hu`) — `fileViewer.exportMd` key, mirroring the structure of the existing `exportHtml` / `exportZip` entries. - `apps/web/src/runtime/exports.test.ts` — three new cases under `exportAsMd`: bytes-verbatim with the markdown content type, title fallback when sanitization yields nothing, and UTF-8 round-trip. The tests stub `URL.createObjectURL` + a minimal `document` so the download path works under `environment: 'node'`. No new dependencies. No daemon-side changes — all client-side. Test plan: - [x] `pnpm --filter @open-design/web test` — 23 files / 143 tests green - [x] `pnpm --filter @open-design/web typecheck` — clean - [x] `pnpm typecheck` (full repo) — clean - [x] Manual browser test via `pnpm tools-dev run web`: clicked the new menu entry on a 10-slide deck artifact; download blob was `text/markdown; charset=utf-8`, 36,990 bytes, started with `<!doctype html>...` matching the Source view byte-for-byte. |
||
|
|
f12471f29d | fix(i18n): update locale coverage after main merge (#251) | ||
|
|
6e11eae675 |
fix(web): treat inlined script content as literal in FileViewer (#343)
`inlineRelativeAssets` reduced the replacements with `next.replace(from, to)` where `to` was the fetched script/stylesheet body. `String.prototype.replace` with a string replacement interprets `$&`, `$'`, `$\``, `$1`–`$9`, and `$<n>` as back-reference patterns, so any inlined file containing one of those sequences (e.g. JSX with `'-$'` currency formatting) had its content mangled — typically with everything-after-the-match spliced into the middle of a string literal, producing "Unterminated string constant" parse errors and preventing the inlined React app from booting. Switching the second arg to a function replacer makes the body literal and fixes the Open path for any project with `$`-bearing inlined assets. Co-authored-by: DevForgeAI CI/CD Engineer <devforge-ai@development.ai> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
0d3de899bd |
fix(daemon): drop literal - argv from codex spawn so prompts deliver via stdin pipe alone (#342)
Recent Codex CLI versions (≥ 0.122 per #237 reports) reject a bare `-` argv element with `error: unexpected argument '-' found`, exiting code 2 before any prompt is read. Open Design currently appends `-` after the positional flags in `apps/daemon/src/agents.ts`, while also setting `promptViaStdin: true` to deliver the prompt via stdin pipe. The `-` became redundant after PR #258 standardized stdin delivery — only its removal was missed for codex, leaving the agent unusable on `main` / 0.2.0 across every platform that runs a current Codex CLI. This matches the pattern that was already corrected for `cursor-agent` upstream of this commit (apps/daemon/src/agents.ts line ~392 comment), where the same `-` sentinel was found to surface as a literal user prompt. The fix here is the codex equivalent: remove the trailing `args.push('-')` and rely on `promptViaStdin: true` for delivery. The existing `agents.test.ts` cases asserted `args.at(-1) === '-'`, which encoded the broken contract; those assertions are dropped and a new regression test pins `args.includes('-') === false` across the plugin, model, reasoning, and disable-plugins permutations so the sentinel can't sneak back in via a future buildArgs branch. Out of scope for this PR (not yet verified, left for separate investigation): - `--full-auto` is also flagged as deprecated by current Codex versions (`use \`--sandbox workspace-write\` instead`). It still parses and runs — the warning is non-fatal — and the right replacement involves pinning the auto-approval policy that `--full-auto` implies, not just `--sandbox`. Cleaning this up safely deserves its own PR after confirming the modern equivalent. - OpenCode (`apps/daemon/src/agents.ts:320`) and Qwen Code (`apps/daemon/src/agents.ts:433`) follow the same `args.push('-')` + `promptViaStdin: true` shape. Whether their current CLIs also reject a bare `-` is unverified; if they do, the same minimal fix applies there. Calling out for maintainer assessment alongside #163 / #218. Closes #237. |
||
|
|
0851df379e |
fix(daemon): serve .jsx / .tsx with JS-family MIME so browser loaders accept them (#340)
Multi-file React prototypes loaded via Babel-standalone (`<script type="text/babel" src="…">`) fetch each `.jsx` / `.tsx` source over HTTP. The daemon's `EXT_MIME` map in `apps/daemon/src/projects.ts` served `.jsx` as `application/octet-stream` (no entry → fallback) and `.tsx` as `text/typescript`, neither of which is a recognized JavaScript MIME. Strict CSPs and browser module loaders reject both, which is one of the failure modes flagged in #336 for the Open-button render path. Map both to `text/javascript; charset=utf-8`, matching the existing treatment of `.js` / `.mjs` / `.cjs`. `.ts` is intentionally left as `text/typescript` — it has no browser-execution path here; tooling that reads `.ts` as TypeScript source is the load-bearing consumer. Tests in `apps/daemon/tests/project-classifiers.test.ts` were updated to pin the new mappings and document the rationale. Refs #336. |
||
|
|
30f8036c9a |
fix(web): make share-menu "Download as .zip" return the actual project tree (#341)
* fix(web): download project tree as zip from share menu The "Download as .zip" share action previously produced a single-file ZIP of the rendered HTML srcdoc. Add a daemon archive endpoint that bundles the on-disk project tree (scoped to the active top-level directory when applicable) and have FileViewer call it, falling back to the in-memory single-file ZIP on failure. UTF-8 filenames are preserved via RFC 5987 Content-Disposition. * fix(daemon,web): address PR #341 review — diagnostics, comments, more tests - Stat the archive root up-front so a missing/non-directory target surfaces a clear ENOENT/ENOTDIR ("does not exist" vs "is empty"), instead of being swallowed by the recursive walk and reported as empty. Distinguishes a deleted project from one with no archivable files for on-call diagnostics. - Document the DEFLATE level-6 choice in the archive builder so future maintainers don't have to guess at the speed/ratio trade-off. - Add a daemon test that the baseName preserves a multi-byte UTF-8 directory name (café-design), covering the path that feeds RFC 5987 filename* on the server. - Add a daemon test that a missing archive root surfaces ENOENT with a "does not exist" message, distinct from the empty-directory case. - Export archiveRootFromFilePath and archiveFilenameFrom for testing, and add web unit tests covering the Content-Disposition fallback chain (UTF-8 filename* → legacy quoted filename= → root slug → title slug, plus malformed-encoding fall-through). - Replace the CJK example string in code comments and tests with a Latin-extended example (café-design) so the codebase stays English while still exercising multi-byte UTF-8 handling. |
||
|
|
648374d839 |
fix(platform): wrap cmd.exe shim invocations to survive /s /c quote stripping (#339)
PR #258 standardized agent spawning through `createCommandInvocation`, which on Windows wraps `.cmd` / `.bat` paths in `cmd.exe /d /s /c <line>` and quotes each argument with cmd-style doubled quotes. PR #232's follow-up fix for `shell:true` was lost in that refactor, and the new shape has its own quoting bug on argv-style spawn: 1. cmd.exe `/s /c` strips exactly one leading and one trailing `"` from the rest of the command line. 2. Node, with `windowsVerbatimArguments` unset, escapes each argv element using CommandLineToArgvW rules — so the inner `"path with space"` ends up surfacing to cmd.exe with an extra layer of `\"` escaping that cmd doesn't understand. Together these collapse `"C:\Users\Ethical Byte\...\codex.CMD" --help` into `C:\Users\Ethical Byte\...\codex.CMD --help` with no quoting preserved, and cmd.exe parses the first space as a token boundary — "`Ethical` is not recognized as an internal or external command." See issue #315 for the full repro. The fix mirrors what Node's own `child_process.spawn({ shell: true })` does internally: wrap the entire joined command line in an extra `"…"` and set `windowsVerbatimArguments: true`. The outer wrap absorbs the `/s /c` strip, leaving inner per-arg quoting intact, and the verbatim flag tells Node to pass argv through to CreateProcess unchanged. Changes: - `packages/platform/src/index.ts` - Extend `CommandInvocation` with optional `windowsVerbatimArguments`. - Extract the cmd.exe shim builder into `buildCmdShimInvocation` and apply the outer wrap + verbatim flag in both `createCommandInvocation` and `createPackageManagerInvocation`. - Forward the flag through `spawnBackgroundProcess` and `spawnLoggedProcess`. - `apps/daemon/src/server.ts` — agent spawn forwards `invocation.windowsVerbatimArguments`. This is the call site that hit #315 in the wild (Codex CLI `.CMD` shim, user dir with space). - `tools/pack/src/win.ts` — `runPnpm` and `runNpmInstall` forward the flag through `execFileAsync`. Affects the Windows packaged-build pipeline when run from a path with spaces. - `tools/dev/src/index.ts` — `runLoggedCommand` accepts and forwards the flag; `buildDesktop` propagates it from `createPackageManagerInvocation`. Affects local dev on Windows. Tests: - 9 new unit tests in `packages/platform/src/index.test.ts` stub `process.platform` so both Windows and POSIX branches run on every CI runner. Coverage: - POSIX pass-through. - Windows non-shim binary pass-through. - `.CMD` shim with spaces in the binary path (the #315 repro). - `.bat` shim parity. - Argv elements with spaces alongside the shim path. - Argv elements without whitespace stay unquoted. - `process.env.ComSpec` fallback. - `npm_execpath` short-circuit (cross-platform). - POSIX pnpm pass-through. - Windows pnpm wrapped through cmd.exe. Closes #315. |
||
|
|
e399593528 |
fix(daemon): increase projectUpload limit from 20MB to 200MB (#319)
The /api/projects/:id/upload endpoint used the projectUpload multer handler with a 20MB limit. Large design files (e.g., 47MB PPTX) were rejected with HTTP 413 Payload Too Large. Bumping to 200MB rather than the originally-proposed 100MB so the cap comfortably covers the heaviest design assets we realistically see — high-fidelity PPTX exports, raw camera images, and PDF source files — without forcing chunked-upload work on the project-side path. The daemon listens on 127.0.0.1 only and writes via multer's disk storage backend, so per-request memory pressure stays bounded. importUpload (separate /api/import/claude-design path) keeps its 100MB limit; the two endpoints don't have to match and bumping import in lockstep is a separate decision. Closes #318 |
||
|
|
afca6b4405 |
feat(desktop): show native Save As dialog for .pptx downloads (#330)
The agent renders PPTX into the project folder, which the UI exposes via /api/projects/:id/raw/*.pptx. Electron's default behavior drops those downloads silently into the OS Downloads folder, so users have no opportunity to pick a destination. Hook will-download on the BrowserWindow session and call setSaveDialogOptions for .pptx so the native Save As panel appears before the download starts. Other formats are unaffected. |
||
|
|
0c65a0508e |
fix(web): keep Design Files view active after deleting a file (#329)
* fix(web): keep Design Files view active after deleting a file (#115) Deleting a file from the Design Files panel was navigating the user into another file's tab (typically the previously-opened file such as `index.html`). Two unrelated symptoms contributed: - `FileWorkspace.handleDelete` always called `setActiveTab(nextActive ?? DESIGN_FILES_TAB)`, which yanked the user out of the Design Files panel whenever `tabsState.active` still pointed at a real file (clicking the Design Files tab only updates local `activeTab`; it does not touch persisted `tabsState.active`). - The row popover had `onMouseDown` propagation guard but no corresponding `onClick` guard on the destructive action path. Fix in `FileWorkspace.handleDelete`: - If the deleted file is the one being viewed (`activeTab === name`), fall back to another open tab (or Design Files if none remain) — the prior behavior, which is correct in this case. - Otherwise (deletion came from the Design Files panel, the typical path), leave `activeTab` alone so the user stays in the list view. Only null out `tabsState.active` when it referenced the deleted file to avoid leaving a dangling pointer for the hydration `useEffect` to resync against. Defensive cleanup in `DesignFilesPanel`: - Add `onClick` propagation guard on the popover wrapper (mirrors the existing `onMouseDown` guard). - Stop propagation on each popover button (Open / Download / Delete) and on the download `<a>` so destructive/menu actions can't bubble into row click handlers under any DOM arrangement. Closes #115 * fix(web): address review feedback + add multi-tab delete regression E2E Review feedback (#329): - Drop the redundant `onClick={(e) => e.stopPropagation()}` on the download `<a>` wrapper; the inner `<button>` is the actual click target and already stops propagation. - Expand the comment in `handleDelete`'s else branch to explicitly state that `activeTab` is preserved because the user is in a different context (Design Files panel or another tab) and shouldn't be navigated away. Regression coverage: - Extend `runDesignFilesDeleteFlow` to upload a sibling file (`keep-me.png`) before `trash-me.png`, so deletion has a fallback tab that the buggy code would have navigated to. After deletion, assert the Design Files tab still has `aria-selected="true"` and the sibling tab is still present (just not auto-activated). |
||
|
|
4f04553948 |
Update docs/assets/github-metrics.svg - [Skip GitHub Action] (#334)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
71370fd9dd |
feat(daemon): add OD_BIND_HOST env var and --host flag for interface binding (#328)
Adds a `host` option to `startServer()` (sourced from `OD_BIND_HOST` env or `--host` CLI flag, defaulting to `'0.0.0.0'` to preserve existing behavior) and wires it through `app.listen(port, host, ...)`. This lets operators restrict the daemon to a single network interface — e.g. a Tailscale IP — for tailnet-only deployments. Without this knob the daemon always bound to all interfaces with no override path. Backward-compatible: the default `'0.0.0.0'` matches current behavior. The reported URL switches from the hardcoded `127.0.0.1` to the actual bound address when a specific interface is requested. Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
b4e00cfb03 |
fix(i18n): add missing designFiles.showMore key to ar/hu/ko/pl/tr (#335)
PR #239 added `designFiles.showMore` to en + 8 other locales but missed ar, hu, ko, pl, tr — `pnpm --filter @open-design/web typecheck` fails with TS2741 on each of those five and the failure has been blocking every PR's CI on `main` since #239 landed. Translations follow the verb/counter conventions already used inside each locale file (ja/zh use "show {n} more"-style; de/es/pt-BR/ru prefer "show +{n} more"-style; ko uses the standard `개` counter). |
||
|
|
223ac39241 |
fix(web): use response-order matching for bulk upload aggregation (#323)
uploadProjectFiles built a Map keyed by `f.originalName ?? f.name` from the server response and matched browser File names against it. The server's `originalName` is the multer-decoded UTF-8 filename, which can diverge from `File.name` under encoding edge cases (NFC vs NFD, double- encoded UTF-8 round-trips, certain CJK combining characters), so the client marked uploads as failed even when the server stored them successfully. The server iterates `req.files` in order and pushes successes to the response array (apps/daemon/src/server.ts), so position is reliable. Replace the name-equality matching with positional matching: every entry in `responseFiles` corresponds to `batch[i]`. The only legitimate partial-failure path is the daemon's `try/catch` around `fs.stat` for files that vanished mid-flight, which produces a shorter response - the unmatched tail of `batch` is then marked failed. The on-disk timestamp prefix in the multer filename callback is intentional (collision avoidance) and unchanged. The user-visible name in the chat composer continues to use `f.originalName ?? f.name`. Fixes #116 Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> |
||
|
|
8897cb85be |
feat(deploy): add /api/projects/:id/deploy/preflight for pre-upload inspection (#320)
* feat(deploy): add /api/projects/:id/deploy/preflight for pre-upload inspection
Today the deploy flow is a single POST that builds the file set, ships
it to Vercel, and waits for the public URL to come up. The user has no
visibility into what is actually being uploaded until the operation
either succeeds or fails with a generic 400. There is no way to tell
"my background image is missing" from "my deploy will exceed Vercel
quotas" without rolling the dice and waiting up to ~110 seconds.
Add a preflight endpoint that builds the plan, runs an analyzer, and
returns a typed report without touching Vercel. The endpoint is purely
additive, costs no network round-trips, and gives the UI everything
needed to render a confirm-before-deploy summary.
Implementation:
- packages/contracts/src/api/projects.ts: new public types
DeployPreflightRequest, DeployPreflightResponse, DeployPreflightFile,
DeployPreflightWarning, and the closed enum DeployPreflightWarningCode
with values broken-reference, invalid-reference, large-asset,
large-bundle, large-html, external-script, external-stylesheet,
no-doctype, no-viewport.
- apps/daemon/src/deploy.ts: refactor buildDeployFileSet so the
walk-and-collect logic lives in a non-throwing buildDeployFilePlan
that returns { entryPath, html, files, missing, invalid }.
buildDeployFileSet keeps its existing semantics by delegating to the
plan and throwing when missing or invalid is non-empty, so every
current caller and test is unchanged. Add analyzeDeployPlan that
walks the entry HTML once via parseHtmlTags + parseHtmlAttributes
(no new parser) and emits the warning vocabulary above. Add
prepareDeployPreflight that combines the plan and the analyzer and
returns the public DeployPreflightResponse shape. Soft thresholds
(4 MiB per asset, 75 MiB bundle, 1 MiB entry HTML) are exported as
DEPLOY_PREFLIGHT_LARGE_*_BYTES constants so tests and future tuning
use the same numbers.
- apps/daemon/src/server.ts: new route POST /api/projects/:id/deploy/
preflight. Same body shape as POST /deploy ({ fileName, providerId? }).
Returns DeployPreflightResponse on success. Validation errors map to
BAD_REQUEST, missing entry file maps to FILE_NOT_FOUND, exactly like
the existing deploy POST so the client error-handling stays uniform.
Tests (apps/daemon/tests/deploy.test.ts, 14 new cases under a new
"deploy plan and analyzer" describe block):
- buildDeployFilePlan returns files plus missing and invalid lists
without throwing.
- buildDeployFileSet still throws on missing/invalid, preserving the
pre-refactor contract.
- Analyzer unit tests for broken-reference, invalid-reference,
no-doctype, no-viewport, external-script, external-stylesheet,
protocol-relative external script, large-asset (per-file but not
entry HTML), large-html (entry HTML threshold), and a healthy-input
case that produces zero warnings.
- Preflight integration tests: full payload shape (provider, entry,
files, totals, warnings) on a healthy project, broken-reference path
on a missing asset, and a non-HTML entry rejection.
Net diff: +439 / -8 across 4 files. No new dependencies, no public API
removals. The existing POST /deploy flow is byte-for-byte unchanged
because buildDeployFileSet is a thin wrapper around the new plan.
* fix(deploy): preflight review fixes (P2 doctype anchoring, P2 large-html source path, P3 logging, P3 jsdoc)
Address review feedback on PR #320:
P2 (Codex bot): the no-doctype check used /<!doctype\s+html/i with no
anchor, so a `<!doctype html>` substring inside a <script> template
literal or HTML comment would mask a missing real declaration. Switch
to `new RegExp('^\uFEFF?\s*(?:<!--[\s\S]*?-->\s*)*<!doctype\s+html', 'i')`
which anchors to the document prolog and accepts the HTML5-permitted
optional BOM, comments, and whitespace before the doctype.
P2 (Codex bot): the large-html warning hardcoded `path: 'index.html'`,
but `entry` in the preflight payload is the source project file (e.g.
`pages/landing.html`) and other warnings already use source paths.
Switch the warning path to `entryPath` so file-targeted UI actions
deep-link back to a real project file.
P3 (lefarcen): add a JSDoc block alongside the inline TS annotation
on analyzeDeployPlan so the function follows the JSDoc-first style
of the rest of the file. The TS inline annotation stays because
deploy.ts uses `// @ts-nocheck`, which makes JSDoc `@param`/`@returns`
non-authoritative for callers; both forms together give docs and
correct caller-side types.
P3 (lefarcen): the preflight route catch block now logs non-DeployError
exceptions via console.error before sending the generic 400, so
unexpected failures show up in the daemon log without leaking
internals to the client.
Tests:
- analyzeDeployPlan flags missing doctype even when a fake `<!doctype>`
lives inside a <script> string literal.
- analyzeDeployPlan accepts a doctype that follows a leading HTML
comment and BOM (HTML5 prolog forms).
- analyzeDeployPlan reports large-html against the source entry path
(e.g. `pages/landing.html`), not the deploy-renamed `index.html`.
38 deploy tests pass, 264 across the daemon package, typecheck clean.
---------
Co-authored-by: Nagendhra <nagendhra405@gmail.com>
|
||
|
|
b06021cd6f |
[codex] Improve project input performance (#239)
* Improve project input performance * update: improvements to input performance * Fix buffered stream cancellation lifecycle |
||
|
|
38946687f8 |
feat(i18n): add full Arabic language support and RTL layout (#316)
* feat(i18n): add full Arabic language support and RTL layout * refactor(i18n): address code review feedback on RTL typography and dev-tool timeouts * fix(i18n): address copilot review (tests, css scoping, readmes) |
||
|
|
0943cb1452 |
fix(deploy): bundle and rewrite assets referenced from inline <style> blocks and style="" attributes (#314)
* fix(deploy): bundle and rewrite assets referenced from inline <style> blocks and style="" attributes buildDeployFileSet only scanned HTML attributes (src/poster/href/srcset) and external .css files for asset references. url() and @import refs inside inline <style> blocks and style="" attributes were silently skipped, so deployed pages were missing their @font-face sources, hero backgrounds, and any image referenced from inline CSS, even though the files existed in the project. Add two helpers in deploy.ts: - extractInlineCssReferences(html) collects url()/@import refs from <style> blocks and style attributes, reusing extractCssReferences and honoring htmlRawTextRanges so style-like text inside scripts and HTML comments is ignored. - rewriteCssReferences(css, baseDir) rewrites url() and bare @import paths the same way rewriteHtmlReference rewrites src/href, so inline CSS keeps working when the entry HTML moves up to the deploy root. Wire both into the existing pipeline: - buildDeployFileSet pushes inline-style refs onto the pending queue alongside HTML and supportingFiles refs. - rewriteEntryHtmlReferences first rewrites <style> block contents, then re-derives htmlRawTextRanges so the tag-attribute pass still skips raw-text regions whose offsets shifted. - rewriteHtmlAttributes treats style="" the same way as srcset, routing through rewriteCssReferences while preserving the original quoting. Tests cover extraction, bundling end-to-end (including @font-face, @import, and subdirectory entries), rewriting of CSS content with remote/data/absolute refs left intact, and the missing-file error path. * fix(deploy): harden inline-CSS pipeline per review (P1 script-literal masking, P2 ReDoS, SVG coverage) Address review feedback on the inline-CSS deploy pipeline: P1 (Codex bot): rewriteEntryHtmlReferences now computes htmlRawTextRanges on the input HTML before the <style> block pre-pass, and skips any <style>...</style> match whose offset falls inside a script string literal or HTML comment. Previously the rewriter mutated those textual fragments, which could change runtime JS behavior on subdirectory deploys. Mirrors the gate already used by extractInlineCssReferences. P2 (lefarcen): tighten the CSS url() and @import regex character classes from .*? to [^)]*? and [^'"]*? respectively. Quoted urls containing literal `)` are now expected to be percent-encoded, which matches CSS authoring conventions and removes the O(n^2) backtracking risk on attacker-controlled CSS. The hardened patterns are shared between extractCssReferences and rewriteCssReferences via CSS_URL_REGEX / CSS_IMPORT_REGEX so the two paths cannot diverge. P3 (lefarcen): add an explicit comment on the rewriteEntryHtmlReferences double-pass so the perf tradeoff is visible inline. New tests: - <style> inside <svg>: extraction and subdir rewriting both work. - <style>-like text inside <script> literals: round-trips byte-for-byte. - <style>-like text inside HTML comments: untouched. - Pathological unclosed url(...... runs in <500ms for 100k input. All 36 deploy tests pass (262 across the daemon package). --------- Co-authored-by: Nagendhra <nagendhra405@gmail.com> |
||
|
|
816cc1d135 |
feat(settings): split API provider protocols (#250)
* feat(settings): split API provider protocols * fix(settings): preserve API protocol config * fix(settings): harden API protocol migration * fix(settings): set apiProviderBaseUrl during v1 migration for known providers |
||
|
|
3d7da43ac8 |
feat(pptx-fidelity): broaden script coverage beyond CJK (#308)
* feat(pptx-fidelity): broaden script coverage beyond CJK The audit skill landed in #307 with CJK-biased depth — italic guidance, font-fallback notes, and line-height tuning all assumed zh-CN / zh-TW / ja / ko as the non-Latin axis. The repo also ships fa (RTL), ru (Cyrillic), de / es / pt-BR / hu (Vietnamese-like Latin Extended), and active discussions for Devanagari / Thai work. This PR closes the obvious gaps without expanding scope to a full RTL / bidi discipline (which is Tier 2 and deserves its own PR). font-discipline.md changes: - Layer 1 gains a "Coverage gap for Latin-slot scripts" callout — Cyrillic / Greek / Vietnamese all go through the Latin slot, but Playfair Display / Source Serif have weak coverage. PowerPoint silently falls back to Calibri mid-paragraph; readers see a styling bug. Includes an `fc-query` snippet for checking coverage. - Layer 5 retitles from "CJK + Latin italic" to "Italic + script interaction" and broadens the rule: italic only applies to scripts with an italic tradition (Latin / Cyrillic / Greek). The implementation function now checks 11 Unicode ranges (CJK, Hebrew, Arabic, Devanagari, Bengali, Thai, Khmer) instead of just CJK. - New "Beyond CJK — other scripts" section: 7-row reference table mapping script family → XML slot (latin / ea / cs) → italic OK? → most common defect → recommended faces. RTL handling is signposted as needing manual review since `verify_layout.py` doesn't check `<a:rtl>` today. - New "Line height per script" section: per-script `Cursor` gap recommendations. Devanagari / Thai / Khmer need 0.16-0.18" headroom vs. the 0.12" Latin default because of stacked diacritics, matras, and tone marks. Latin with Vietnamese Extended (ếẫỗ) needs 0.14". - Audit checklist updated to reflect the broader rule and adds a "Beyond CJK" line for RTL / non-Latin-CJK content. SKILL.md: anti-pattern entry rewritten from "Italicizing CJK display type" to "Italicizing scripts that have no italic tradition", listing all 6 affected script families. layout-discipline.md: Cursor section gains a per-script `gap` tuning callout pointing at the new font-discipline.md table. Sanity-tested the Unicode range table against representative strings in 7 scripts; all classifications correct. Out of scope for this PR (Tier 2 follow-ups): - Full RTL discipline reference (rtl-discipline.md): bidi rules, margin mirroring, paragraph-direction propagation. - verify_layout.py --rtl mode: assert <a:rtl val="1"/> on RTL slides. - verify_layout.py glyph-coverage check: would need fonttools and per-run script detection. - Vertical text (tategaki) and Furigana for traditional Japanese layouts. * fix(pptx-fidelity): add Lao to NO_ITALIC_RANGES The prose in font-discipline.md and the "Beyond CJK" table both list Thai / Lao / Khmer as scripts without an italic tradition, but the NO_ITALIC_RANGES tuple in the implementation snippet only covered Thai and Khmer. Implementers copying the function would have rendered Lao runs with synthesized italic — exactly the slant defect this skill is meant to prevent. Adds Lao (U+0E80-U+0EFF) between the existing Thai and Khmer rows. Sanity-checked against ສະບາຍດີ. Caught by Codex on #308. * fix(pptx-fidelity): address review feedback from #308 P2 fixes (lefarcen): - Layer 1 coverage check now includes a Windows snippet (PowerShell + System.Drawing) and a cross-platform fontforge fallback so Windows users aren't stranded when the prose said the skill is cross-platform. - "Beyond CJK" Arabic / Hebrew / Persian row gains a "RTL discipline scope" callout that quantifies the remaining gap as ~15-20% of the font + layout surface area (Unicode TR9 bidi, chrome / footer mirroring, kashida + line-fill, right-anchored alignment) so readers can judge whether to wait for the Tier 2 follow-up or scope it themselves. - NO_ITALIC_RANGES expands from 12 to 19 ranges. The earlier table asserted "Devanagari / Bengali" as no-italic but stopped there; the principle ("no italic tradition") covers all major Indic scripts. Added Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, and Malayalam, with an inline comment explaining the principle so future maintainers can add Sinhala / Tibetan when needed. P3 polish: - "Line height per script" gains a sources note: numbers measured against Noto Sans / Noto Serif / IBM Plex line-height for 14pt body with full diacritic stacks (Devanagari conjuncts, Thai 4-mark sequences, stacked Vietnamese ỗ). - Audit checklist's "Beyond CJK" entry now includes the unzip + grep command for verifying <a:rtl val="1"/> propagation, matching the verification style used in Layer 4. - add_run_with_italic_safety docstring documents what cs_face=None means: safe for Latin-only decks, omitted by set_run_fonts when no complex-script characters appear. - layout-discipline.md gap callout now explains how to detect the highest-demand script in a mixed deck — scan against the Layer 5 Unicode ranges (extended with Vietnamese U+1E00-1EFF), pick the max-gap per slide, take the deck-wide max for a uniform setting. Sanity-tested NO_ITALIC_RANGES against representative strings in 15 scripts; all classifications correct. |
||
|
|
bec2383c60 |
docs: update Japanese document (#309)
* docs: add Japanese QUICKSTART * docs: update TRANSLATIONS.md |
||
|
|
6fa2077651 |
feat(web): add pet companion with Codex hatch-pet integration (#296)
* feat(web): add pet companion with Codex hatch-pet integration Introduces a customizable floating pet companion (overlay + entry-view rail + composer menu + dedicated Settings → Pets section) that supports built-in pets, user customization (glyph/image/spritesheet), and one-click adoption of pets packaged by the upstream Codex `hatch-pet` skill via a new `/api/codex-pets` daemon endpoint. Vendors the unmodified `hatch-pet` skill under `skills/hatch-pet/` and adds i18n strings across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(scripts): sync community Codex pets from public catalogs Adds `pnpm sync:community-pets` which fetches all pets from codex-pet-share.pages.dev (paginated Supabase Functions API) and j20.nz/hatchery (single-shot JSON), then writes each one as `<id>/pet.json` + `<id>/spritesheet.webp` under `\${CODEX_HOME:-\$HOME/.codex}/pets/`. The existing daemon `codex-pets` registry already scans that folder, so synced pets appear under Settings → Pets → Recently hatched and adopt with one click — no manual upload. Supports --source/--out/--force/--limit flags and validates magic bytes so HTML error pages never end up masquerading as `.webp` files. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(daemon): tighten codex-pets validation and document vendoring - sanitizeId now rejects ids that still contain `..` after collapsing, closing a defensive gap on the path-traversal guard for the `/api/codex-pets/:id/spritesheet` route. - listCodexPets emits the sanitised folder name as the public id so the download route resolves directly against the on-disk folder, even when `manifest.id` differs (manual drops, sanitiser-touched manifests). - Drop `@ts-nocheck` from `codex-pets.ts`; module is now strict-typed with explicit interfaces, an unknown-narrowed JSON.parse path, and a `pickString` helper guarding manifest fields one by one. - Restrict the spritesheet response CORS header to sandboxed-iframe callers (Origin: null) instead of unconditional `*`, matching the existing raw-file route pattern. Same-origin web traffic does not need the header (web proxies `/api/*` through the daemon). - Add `skills/hatch-pet/README.md` explaining the vendoring trade-off, provenance, and re-sync procedure. - Add `docs/codex-pets.md` covering where pets live, how to populate the registry without Codex installed, and the manifest contract. Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code) * fix(i18n): add pet.* keys to Hungarian locale Hungarian locale was added on main after this branch diverged, so the new pet.* dictionary keys never landed there and tsc -b reports hu's Dict as incomplete once main is merged in. Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code) * feat(web): atlas-driven pet animations + bundled community pets Builds on the existing pet companion (#296) with a richer animation loop, a curated set of community pets that ship with the repo, and a one-click sync into ~/.codex/pets/. - Atlas-mode rendering: PetSpriteFace can now play the full Codex 8x9 sprite atlas and swap rows from a JS-driven frame index. PetOverlay classifies pointer interactions (idle / hover / drag-direction / long-idle waiting) and maps them to the matching atlas row, so the pet waves on hover, runs on drag, and falls into a waiting pose after 6s of stillness. Single-strip pets keep their existing CSS steps() animation, with the steps timing fixed to jump-none so frame cells line up on cell boundaries. - Atlas adoption: PetSettings exposes both "Use full atlas (animated)" and "Freeze to this row" — full mode keeps every row for the interaction state machine, single-row mode crops one strip via the existing canvas helper. New prepareCodexAtlas downscales the atlas to a localStorage-friendly PNG while preserving the grid layout. - Settings tabs: pet sources are now split into Built-in / Custom / Community tabs so each origin gets its own dedicated surface. - Bundled pets: scripts/bake-community-pets.ts seeds a curated set (clippit, dario, nyako-shigure, slavik, trump, tux, yelling-dario, yorha-sit-2b) into assets/community-pets/. The daemon scans this alongside the user's ~/.codex/pets/ root, with user pets winning when ids collide. CodexPetSummary gains a `bundled` flag so the UI can tag those cards with a "Bundled" pill. - One-click community sync: daemon-side port of sync-community-pets exposed via POST /api/codex-pets/sync. Returns the same wrote/skipped/failed/total summary the CLI prints. Web Pet settings surface this as a "Download community pets" button under the Community tab. - Avatar dropdown + hide rail: EntryView's avatar button is now a small menu (mirrors the project-view AvatarMenu) with toggles for hiding/showing the pet rail and opening Settings. PetRail gets a matching × button for the same hide flow. - Locales: 7 new pet.* keys for tabs, sync, hide/show, atlas full mode, and the Bundled pill — translated into all 13 supported locales. Typechecks pass across all workspace packages; daemon + web vitest suites stay green. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): bundled-pets built-in tab, ambient atlas animations, and community sync button The Built-in tab now sources its catalog from the bundled spritesheets at `assets/community-pets/` instead of the eight emoji placeholders that felt boring next to the Codex hatch-pet atlases. - Daemon: `listCodexPets` flags `bundled: true` by curated-set membership in `assets/community-pets/`, not by which folder the sprite happened to be read from. Previously a fully-synced user inbox preempted every bundled id and left the tab empty. - Settings → Pets → Built-in renders the same sprite-card grid as Community, filtered by `bundled: true`, and reuses the existing `adoptCodexPet` flow. Community tab filters to non-bundled so the curated set never appears twice. - Community tab gains the long-promised "Download community pets" trigger that calls `/api/codex-pets/sync` and shows an inline status line for the run summary. Strings already existed in every locale; we just plumbed the button. - `PetOverlay` gets ambient atlas-row choreography — while idle, the overlay occasionally swaps `idle` for a random non-idle row (wave / hop / look) so the pet doesn't feel frozen. User gestures cancel the beat and take over instantly. `pickAmbientRow` lives next to `pickAtlasRow` so both row pickers share the fallback discipline. - One-shot `migrateCustomPetAtlas` heals configs adopted before the overlay learned row switching by re-downloading the full spritesheet so hover / drag / ambient variety light up on next launch. - `BUILT_IN_PETS` is now an empty array (the type stays for backwards compat); legacy configs whose `petId` still points at an emoji id (`mochi`, `pixel`, …) fall back to the user's custom slot in `resolveActivePet` so the overlay never renders blank. - i18n: refresh `pet.tabBuiltInHint` (drop "emoji companions") and add `pet.builtInEmpty` across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
254a9ff479 |
feat(skills): add pptx-html-fidelity-audit + wire into export prompt (#307)
* feat(skills): add pptx-html-fidelity-audit + wire into export prompt Adds a python-pptx audit skill that catches the silent drift modes between an HTML deck and its PPTX export — footer overflow, off-canvas shapes, missing italic/em, hero slides not centered, oversized bounding boxes, lost CJK type slots. The skill ships: - SKILL.md — five-step workflow (extract dump → walk HTML → audit table → re-export with footer-rail + cursor-flow → verify) - references/layout-discipline.md — Cursor primitive, hero budget centering, per-slide-type code snippets - references/font-discipline.md — five-layer font audit: mapping → presence → variable-vs-static → XML latin/ea/cs slots → CJK + Latin italic interaction - references/audit-table-template.md — fixed table format - scripts/extract_pptx.py — dump every shape (text, geometry, per-run typography) as JSON - scripts/verify_layout.py — assert footer-rail + canvas invariants; nonzero exit on any violation Wires the skill into the FileViewer "Export as PPTX" flow: - handleExportAsPptx in ProjectView.tsx now instructs the agent to apply the footer-rail discipline up front (CONTENT_MAX_Y = 6.70", Cursor + budget centering, explicit a:latin / a:ea slots), then run verify_layout.py as a mandatory gate before declaring done. - If 🟡 typography issues surface, the prompt points the agent at font-discipline.md. Verifier exit code is the gate: agent cannot claim "fidelity correct" without 0 rail violations. If the environment lacks python-pptx, the agent must say so explicitly rather than eyeball the deck. * fix(pptx-fidelity): address review feedback from #307 P2 fixes (codex): - ProjectView prompt now quotes the verifier path so filenames with spaces (e.g. "Q2 review.pptx") parse as a single argument instead of two. - extract_pptx color extraction no longer gates on font.name/size — a run can inherit name/size from the theme yet set its own color, and that drift is exactly what the audit needs to catch. P3 polish (lefarcen): - SKILL.md "When this skill applies" now shows a 7-line HTML snippet so readers can self-assess scope at a glance. - Cursor.take(gap=0.12) gets an inline comment explaining the default is ~1 line of whitespace at 14pt, adjustable per design system. - New "Customizing the rails" callout under the constants block, with pointers to the verifier flags (--content-max-y, --canvas-h/w) and the export-prompt now documents the same path. - New "Why geometry-based verification, not visual diff" section captures the design tradeoff (platform lock-in, imprecision, setup cost) that previously only lived in the PR body. - font-discipline.md Layer 2 now includes a PowerShell snippet for Windows font discovery alongside fc-list / mdfind. - layout-discipline.md gains a "How to use this file" header so readers know to skim §1-3 then jump to slide-type snippets. Also: scripts/.gitignore for __pycache__. * fix(skills): register pptx-html-fidelity-audit in German display metadata CONTRIBUTING.md requires every skill in skills/ to have a corresponding German display metadata entry in apps/web/src/i18n/content.ts; the content.test.ts assertion catches drift. The CI failure on #307 was this check tripping on the new skill. Adds the German example prompt entry. Test suite passes locally. |