Skip to content

fix(desktop): persist generated artifacts so designs reload correctly#93

Merged
hqhq1025 merged 2 commits intomainfrom
fix/persist-generated-artifacts
Apr 19, 2026
Merged

fix(desktop): persist generated artifacts so designs reload correctly#93
hqhq1025 merged 2 commits intomainfrom
fix/persist-generated-artifacts

Conversation

@hqhq1025
Copy link
Copy Markdown
Collaborator

Bug

After a successful generation the renderer showed the artifact, but reopening the design later showed only an empty assistant message wrapper (```html\n\n```). Inspection of ~/Library/Application Support/@open-codesign/desktop/designs.db confirmed the cause:

  • designs: 2 rows
  • design_messages: contained the user prompt + an assistant text wrapper
  • design_snapshots: 0 rows

The generate.ok IPC log reported artifacts: 1, so the artifact reached the renderer fine — but the persist path (persistDesignState) only wrote messages + thumbnail and never created a snapshot. switchDesign reads previewHtml from the latest snapshot, so the preview came back empty on reload.

Fix (option a — write to design_snapshots)

That table is already purpose-built (artifact_type, artifact_source, prompt, parent_id chain) and the IPC channel snapshots:v1:create was already wired through preload. We:

  1. Pass the produced artifact (type, content, prompt, message) into persistDesignState after both applyGenerateSuccess and applyInlineComment.
  2. Look up the latest snapshot for the design to get parentId; first generation uses type: 'initial', subsequent ones type: 'edit'.
  3. Map core's Artifact.type ('html' | 'svg' | 'slides' | 'bundle') onto the snapshot column's allowed values ('html' | 'react' | 'svg'); slides / bundle fall through to 'html' because their on-disk source is HTML — keeps the CHECK constraint stable, no migration needed.
  4. switchDesign already rehydrates previewHtml from snapshots[0].artifactSource, so the reload path now restores the artifact correctly.

Option (b) — embedding the source into the assistant message body — was rejected because it would duplicate large HTML blobs into chat history, defeat existing snapshot lineage / parent_id chaining, and waste the schema we already pay for.

Tests

New Vitest in apps/desktop/src/renderer/src/store.test.ts covering the full round-trip:

  • Mock generate to return one HTML artifact.
  • Run sendPrompt against a known currentDesignId.
  • Assert snapshots.create was invoked with { type: 'initial', artifactType: 'html', artifactSource, prompt, parentId: null } and that the in-memory snapshots store now holds the row.
  • Wipe in-memory state to simulate a fresh app load, call switchDesign, and assert previewHtml + messages are restored.

All 289 desktop tests pass; lint + typecheck clean.

PRINCIPLES check

  • Compatibility: green — no schema bump, existing rows stay valid, no IPC channel signature changed.
  • Upgradeability: green — reuses the schema-versioned snapshots:v1:* channels and DesignSnapshotV1.schemaVersion = 1.
  • No bloat: green — zero new dependencies, ~60 LOC of store wiring + a small mapping helper.
  • Elegance: green — the persist path now mirrors the read path (snapshots in / snapshots out); no parallel storage formats.

Generated artifacts were never written to design_snapshots, so reopening a
design showed only an empty assistant message wrapper while the SQLite
designs.db reported `design_snapshots: 0 rows`. The renderer's persist path
saved messages + thumbnail but skipped snapshot creation entirely.

Now applyGenerateSuccess (and applyInlineComment) feed the produced artifact
into persistDesignState, which writes a new design_snapshots row chained to
the previous one (`type: 'initial'` for the first generation, `'edit'`
afterwards). switchDesign already rehydrates previewHtml from the latest
snapshot, so reopening a design now restores the artifact instead of a blank
preview.

Existing schema is reused — no schema_version bump or migration needed.
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Findings

  • [Major] Silent fallback hides unsupported artifact types and can persist invalid snapshot metadata without surfacing an error, which conflicts with the no-silent-fallback constraint. Evidence apps/desktop/src/renderer/src/store.ts:289 and apps/desktop/src/renderer/src/store.ts:322.
    Suggested fix:
    function toSnapshotArtifactType(coreType: string | undefined): 'html' | 'react' | 'svg' {
      switch (coreType) {
        case undefined:
        case 'html':
        case 'slides':
        case 'bundle':
          return 'html';
        case 'svg':
          return 'svg';
        case 'react':
          return 'react';
        default:
          throw new Error(`Unsupported artifact type for snapshot persistence: ${coreType}`);
      }
    }

Summary

  • Review mode: initial
  • 1 issue found in added/modified lines.
  • docs/VISION.md and docs/PRINCIPLES.md: Not found in repo/docs.

Testing

  • Not run (automation)

open-codesign Bot

if (coreType === 'svg') return 'svg';
if (coreType === 'react') return 'react';
return 'html';
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Major] This default return 'html' silently accepts any unknown artifact type. Please make this mapping exhaustive and throw on truly unsupported types so persistence errors are surfaced instead of hidden.

function toSnapshotArtifactType(coreType: string | undefined): 'html' | 'react' | 'svg' {
  switch (coreType) {
    case undefined:
    case 'html':
    case 'slides':
    case 'bundle':
      return 'html';
    case 'svg':
      return 'svg';
    case 'react':
      return 'react';
    default:
      throw new Error(`Unsupported artifact type for snapshot persistence: ${coreType}`);
  }
}

@hqhq1025
Copy link
Copy Markdown
Collaborator Author

Addressed Codex's [Major] silent-fallback finding on toSnapshotArtifactType in a7c2ce0 — unknown core ArtifactType values now throw instead of being persisted as html. Known cases (html/slides/bundle/undefined -> html, svg, react) stay explicit, and a new vitest case covers the throw path. pnpm --filter @open-codesign/desktop test green (292 tests).

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Findings

  • No high-confidence issues found in added/modified lines.

Summary

  • Review mode: follow-up after new commits
  • Prior bot finding on silent artifact-type fallback appears resolved by exhaustive mapping + explicit throw in apps/desktop/src/renderer/src/store.ts:291.
  • docs/VISION.md and docs/PRINCIPLES.md: Not found in repo/docs.
  • Residual risk: no assertion in this diff for parent chaining across multiple persisted snapshots (initial -> edit) or the applyInlineComment persistence path.

Testing

  • Not run (automation). Suggested additions: Vitest coverage for sequential snapshot writes verifying parentId chaining and one applyInlineComment persistence case.

open-codesign Bot

@hqhq1025 hqhq1025 merged commit 444fde3 into main Apr 19, 2026
5 of 6 checks passed
@hqhq1025 hqhq1025 deleted the fix/persist-generated-artifacts branch April 19, 2026 07:27
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