Skip to content

fix(settings,layout): import keeps the local OSC target; inline layouts export as JSON, not the stale default preset - #135

Merged
pyramation merged 3 commits into
mainfrom
fix/inline-layout-env-import-osc
Sep 21, 2026
Merged

pyramation merged 3 commits into
mainfrom
fix/inline-layout-env-import-osc

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

Two issues found while syncing the Grace receiver laptop to the 28-fixture brain (both flagged by the laptop-side assistant, reproduced here).

1. importProject --overwrite wiped the machine's OSC target. A bundle exported from the brain has no osc block (the brain sends nowhere), so re-importing it over the laptop's grace project replaced the config wholesale and dropped the BEYOND host/port. The OSC target is a fact about the hardware next to this machine, not portable project state:

// importProject, on overwrite:
if (hasProject(name) && bundle.config.osc == null && localConfig.osc is non-empty)
  config = { ...bundle.config, osc: localConfig.osc }   // result.keptLocalOsc = true

A bundle that does carry osc still wins. ImportResult/ImportSummary gain keptLocalOsc, surfaced in the CLI import output and the desktop import dialog.

2. WAVEGRID_LAYOUT leaked grid-7x7 for inline layouts. configEnvMap exported config.layout.preset, but a store layer with {kind:'rings', …} is merged over DEFAULT_CONFIG, so preset:'grid-7x7' survives underneath kind. resolveLayout prefers kind, so the running process was right, but wavegrid env export / receiver env handed a 49-cannon preset to anything reading the env. Now:

if (layout.kind) env.WAVEGRID_LAYOUT = JSON.stringify(layout)   // full inline spec
else if (layout.preset) env.WAVEGRID_LAYOUT = layout.preset

and envLayer accepts either form ({-prefixed → JSON.parse as LayoutSpec, else preset id), so the value round-trips: the test exports the inline grace-28 rings and reloads it to 28 cannons.

Tests: layout 108, settings 87, desktop 99, cli 147 passing; tsc clean.

Link to Devin session: https://app.devin.ai/sessions/c4872c2982734093bdee80d04a7d5ceb
Open in Devin Desktop: https://app.devin.ai/desktop/session/c4872c2982734093bdee80d04a7d5ceb?variant=devin
Requested by: @pyramation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@tenki-reviewer

tenki-reviewer Bot commented Sep 21, 2026

Copy link
Copy Markdown

Review complete. 🟠 1 high

💬 Inline comments (1)

🧹 Nitpicks (1) — 🟢 1 low
  • 🟢 Guard JSON.parse in parseLayoutEnv (config.ts:52) — parseLayoutEnv calls JSON.parse(text) unguarded at packages/layout/src/config.ts:52 whenever a WAVEGRID_LAYOUT value starts with {.

This change hardens the portable project export/import path so importing a bundle onto a machine that already has a project no longer clobbers hardware-local state, and adds a JSON-based env-var path for loading layout config.

Files Change
packages/settings/src/portable.ts + tests Replaces unconditional overwrite with a branch that keeps a machine's local OSC target (BEYOND/FB4/routing) when the incoming bundle carries no target config.
packages/layout/src/config-env.ts, config.ts + tests Adds parseLayoutEnv that parses a JSON WAVEGRID_LAYOUT spec, with kind/preset validation and merged-layout application.
packages/cli/src/commands/project-io.ts Wires the overwrite-aware import into the CLI project-io command.
packages/desktop/.../transfer-dialogs.tsx, types/ipc.ts Mirrors the import summary/overwrite result back across the IPC boundary to the renderer.

Reviewed commit: a5320f0

@tenki-reviewer tenki-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improves portable project export/import fidelity by preserving machine-local OSC targets on overwrite and adds env-driven layout loading via JSON parsing.

Key findings

// with no target must not wipe the one configured here.
let config = bundle.config;
let keptLocalOsc = false;
if (hasProject(paths, project) && config.osc == null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 bug · high

Empty osc: {} bundle wipes local OSC target

On overwrite, importProject preserves a machine's local OSC target only when config.osc == null (packages/settings/src/portable.ts:151). But the canonical "no target" form saved by wavegrid projects osc clear is config.osc = {} (packages/cli/src/commands/osc.ts:174), which exportProject ships verbatim, so an empty object fails the == null test and the branch is skipped.

The locally configured BEYOND/FB4/routing target is then silently overwritten with the bundle's empty block — exactly the cross-machine wipe this PR exists to prevent. Reuse the empty-object semantics already applied to the local side at line 153 on the bundle side.

📋 Prompt for AI Agents

In packages/settings/src/portable.ts at line 151, change the overwrite guard so an empty osc object is treated as "bundle had no target": replace config.osc == null with (config.osc == null || Object.keys(config.osc).length === 0). The local-keep branch at lines 152-157 then fires for a bundle whose config carries osc: {} (the shape persisted by wavegrid projects osc clear), preventing the import from wiping this machine's configured BEYOND/FB4/routing hardware target on overwrite. Short-circuiting keeps Object.keys safe for null/undefined, and any non-object osc yields zero keys, preserving the local target.

@pyramation
pyramation merged commit a6e6732 into main Sep 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant