Skip to content

Fix v3 config persistence; add tabbed workspace UI#236

Open
Aleel101 wants to merge 7 commits intoOpenCoworkAI:mainfrom
Aleel101:main
Open

Fix v3 config persistence; add tabbed workspace UI#236
Aleel101 wants to merge 7 commits intoOpenCoworkAI:mainfrom
Aleel101:main

Conversation

@Aleel101
Copy link
Copy Markdown


Summary

This PR introduces a targeted config persistence fix alongside a cohesive desktop UX refinement built on the existing workspace model.

  • Base: 6 commits on Aleel101/main (e.g. 88046c2) vs OpenCoworkAI/upstream/main
  • Compare: git fetch upstream && git log upstream/main..HEAD or git diff upstream/main...HEAD

Key Changes

Config Persistence Fix (v3 optionals)

Fixes an issue where optional v3 config fields (modelFast, imageGeneration, designSystem) could be unintentionally dropped during partial config writes.

  • Introduces preservedV3OptionalsForWrite in shared config
  • Threads preservation through all write paths (onboarding, OAuth, image generation, etc.)
  • Ensures unrelated config updates no longer remove optional fields
  • Includes changeset: .changeset/persist-fast-model-v3.md

Desktop UX and Workspace Model

Refines how existing features are organized and presented rather than introducing entirely new surfaces.

  • Tabbed canvas: Files · History · Code are now first-class tabs
  • Layout updates:
    • Custom top window bar
    • Language/theme controls moved to Settings (removed from main chrome)
  • Interaction improvements:
    • Model switcher (search and layout improvements)
    • Chat “thinking” and turn affordances
    • Todos as a fixed top region with show/hide behavior

Note: Console and History views already existed. This PR focuses on integrating them into the tabbed layout and improving UX consistency.


Type of Change

  • Bug fix — preserves v3 optional config fields across writes
  • Feature — tabbed workspace model and UX improvements
  • Refactor — layout, model switcher, chat/todo presentation
  • Tooling — lint, types, tests, lockfile alignment
  • Breaking (minor UX) — language/theme controls moved to Settings

Checklist

  • pnpm lint, typecheck, and test pass locally
  • Tests updated where config, store, or preview logic changed
  • Changeset included for config and UX updates
  • Docs updated if aligning public-facing behavior

Dependencies

No new product dependencies. Any package.json or lockfile changes are version alignment only.


Screenshots

  • Custom window chrome (top bar, no inline theme/language controls)
  • Tabbed canvas (Files · History · Code)
  • Todos fixed top region (toggle visibility)
  • Model switcher improvements
  • Chat affordances
  • Console and History within tabbed layout

(See attached images)


image
image
image
image
image

p.s. Do what ya like, just fixed some things that were annoying me and thought some people may like these changes as well.

- Downgraded `@tailwindcss/postcss` and `tailwindcss` to version 4.0.0.
- Downgraded `electron` from 39.8.9 to 39.8.8.
- Added IPC handlers for window management (minimize, maximize, close).
- Updated window creation to remove the native title bar and manage menu visibility based on platform.
- Enhanced state management in onboarding and design generation processes.
- Introduced support for fast model configuration in onboarding IPC.
- Improved design generation state handling to support multiple active generations.
- Refactored various components to utilize the new state management logic.
- Introduced `preservedV3OptionalsForWrite` function to maintain optional fields during configuration updates.
- Updated various IPC handlers to utilize the new function, ensuring optional fields like `designSystem` and `imageGeneration` are preserved when writing configurations.
- Added tests for the new functionality to verify correct behavior in different scenarios.
- Adjusted dependencies in `pnpm-lock.yaml` to reflect version changes for several packages.
- Reorganized imports and adjusted formatting in several files for better readability.
- Enhanced the structure of function parameters and return values for clarity.
- Made minor adjustments to the console and files panels for consistent styling.
- Updated the handling of console logs and design generation states for improved maintainability.
- Refactored the ModelSwitcher and PreviewToolbar components to streamline their logic and enhance user experience.
… tests

- Updated onboarding state tests to include a new `modelFast` field initialized to null.
- Enhanced the `PreviewPane` test to include an `onConsoleLog` handler for better logging during tests.
- Ensured consistency across various test files by adding the `modelFast` field to relevant configurations.
- Added `activeGenerations` state to `useCodesignStore` for tracking ongoing design generations.
- Updated `ConsolePanel` to improve log entry keys for better uniqueness and stability.
- Refactored dependencies in `ModelSwitcher` and `StickyTodoHeader` to ensure proper state handling and performance.
- Updated `CanvasTab` type to include 'history' and 'code' tabs, improving tab management.
- Refactored `CanvasTabBar` to display new tab types with appropriate icons and labels.
- Adjusted `PreviewPane` to handle new tab types, ensuring correct rendering of content based on active tab.
- Enhanced localization by adding translations for new tab labels in English, Portuguese, and Chinese.
- Improved overall UI consistency and user experience across components related to canvas tabs.
@github-actions github-actions Bot added docs Documentation area:desktop apps/desktop (Electron shell, renderer) area:core packages/core (generation orchestration) area:providers packages/providers (pi-ai adapter, model calls) area:ui packages/ui (design system) area:build Turbo/Vite/Biome/tsconfig toolchain labels Apr 26, 2026
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] Fast-model loading can get stuck forever or fail silently when provider model discovery fails — apps/desktop/src/renderer/src/components/Settings.tsx:2041, apps/desktop/src/renderer/src/components/Settings.tsx:2260. listForProvider() is only handled in .then(...): a rejected IPC/network call never clears listLoading, and an { ok: false } response quietly downgrades the UI to an empty/manual state with no error surfaced.
    Suggested fix:

    void window.codesign.models
      .listForProvider(config.provider)
      .then((res) => {
        if (cancelled) return;
        setListLoading(false);
        if (res.ok) {
          setAvailableModels(res.models);
          return;
        }
        setAvailableModels([]);
        pushToast({
          variant: 'error',
          title: t('settings.modelRouting.saveFailed'),
          description: res.message,
        });
      })
      .catch((err) => {
        if (cancelled) return;
        setListLoading(false);
        setAvailableModels([]);
        pushToast({
          variant: 'error',
          title: t('settings.modelRouting.saveFailed'),
          description: cleanIpcError(err) || t('settings.common.unknownError'),
        });
      });
  • [Major] Restoring a snapshot updates the preview HTML but leaves comment filtering on the old snapshot — apps/desktop/src/renderer/src/components/HistoryPanel.tsx:53, apps/desktop/src/renderer/src/components/HistoryPanel.tsx:61, with the stale filter applied in apps/desktop/src/renderer/src/components/PreviewPane.tsx:405 and apps/desktop/src/renderer/src/components/PreviewPane.tsx:507. The new snapshot returned by snapshots.create() is ignored, so currentSnapshotId is never refreshed and pins/comments can belong to the previous version after restore.
    Suggested fix:

    const loadCommentsForCurrentDesign = useCodesignStore((s) => s.loadCommentsForCurrentDesign);
    
    const restored = await window.codesign.snapshots.create({
      designId: currentDesignId,
      parentId: selected.id,
      type: 'fork',
      prompt: selected.prompt,
      artifactType: selected.artifactType,
      artifactSource: selected.artifactSource,
    });
    setPreviewHtml(restored.artifactSource);
    await loadCommentsForCurrentDesign();
  • [Minor] The new console panel bypasses the shared token system with raw colors and a hardcoded mono stack — apps/desktop/src/renderer/src/components/ConsolePanel.tsx:9, apps/desktop/src/renderer/src/components/ConsolePanel.tsx:18, apps/desktop/src/renderer/src/components/ConsolePanel.tsx:28, apps/desktop/src/renderer/src/components/ConsolePanel.tsx:81. This conflicts with the app-chrome rule to route UI values through packages/ui tokens.
    Suggested fix:

    const LEVEL_BG: Record<ConsoleLevel, string> = {
      log: '',
      info: '',
      warn: 'bg-[color-mix(in_srgb,var(--color-warning)_6%,transparent)]',
      error: 'bg-[color-mix(in_srgb,var(--color-error)_6%,transparent)]',
      debug: '',
    };
    
    <div className="... font-[var(--font-mono)] ..." />
    <span className="... bg-[var(--color-warning)] text-[var(--color-on-accent)] ..." />

Summary

  • Review mode: initial. Three issues found: the fast-model loader has an unhandled failure path, snapshot restore does not resync snapshot-scoped comments, and the new console UI reintroduces raw app-chrome values instead of packages/ui tokens.
  • Residual risk: I did not find coverage for the fast-model discovery failure path or for history-restore snapshot/comment synchronization.

Testing

  • Not run (automation)

open-codesign Bot

setQuery('');
setListLoading(true);
setAvailableModels([]);
void window.codesign.models.listForProvider(config.provider).then((res) => {
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.

This listForProvider() call needs a failure path. Right now a rejected IPC/network call never clears listLoading, and an { ok: false } response silently degrades into an empty/manual model picker instead of surfacing the error.

Suggested fix:

void window.codesign.models
  .listForProvider(config.provider)
  .then((res) => {
    if (cancelled) return;
    setListLoading(false);
    if (res.ok) {
      setAvailableModels(res.models);
      return;
    }
    setAvailableModels([]);
    pushToast({
      variant: 'error',
      title: t('settings.modelRouting.saveFailed'),
      description: res.message,
    });
  })
  .catch((err) => {
    if (cancelled) return;
    setListLoading(false);
    setAvailableModels([]);
    pushToast({
      variant: 'error',
      title: t('settings.modelRouting.saveFailed'),
      description: cleanIpcError(err) || t('settings.common.unknownError'),
    });
  });

artifactType: selected.artifactType,
artifactSource: selected.artifactSource,
});
setPreviewHtml(selected.artifactSource);
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.

The restore path creates a new snapshot but never refreshes currentSnapshotId, so PreviewPane and CommentsPanel keep filtering pins/comments against the previous snapshot while the HTML switches to the restored version.

Suggested fix:

const loadCommentsForCurrentDesign = useCodesignStore((s) => s.loadCommentsForCurrentDesign);

const restored = await window.codesign.snapshots.create({
  designId: currentDesignId,
  parentId: selected.id,
  type: 'fork',
  prompt: selected.prompt,
  artifactType: selected.artifactType,
  artifactSource: selected.artifactSource,
});
setPreviewHtml(restored.artifactSource);
await loadCommentsForCurrentDesign();


const LEVEL_STYLE: Record<ConsoleLevel, string> = {
log: 'text-[var(--color-text-primary)]',
info: 'text-[#3b82f6]',
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.

This new panel reintroduces raw app-chrome values (#f59e0b, #3b82f6, ui-monospace,Menlo,monospace) instead of going through packages/ui tokens, which conflicts with the repo's token-only UI rule.

Suggested fix:

const LEVEL_BG: Record<ConsoleLevel, string> = {
  log: '',
  info: '',
  warn: 'bg-[color-mix(in_srgb,var(--color-warning)_6%,transparent)]',
  error: 'bg-[color-mix(in_srgb,var(--color-error)_6%,transparent)]',
  debug: '',
};

<div className="... font-[var(--font-mono)] ..." />
<span className="... bg-[var(--color-warning)] text-[var(--color-on-accent)] ..." />

…ings panel

- Replace lang-mode selector (auto/html/js) with unified html language mode
- Add Shiki v4 TextMate grammar tokenization via @shikijs/monaco for VS Code-quality
  syntax highlighting across HTML, CSS, JS, and embedded regions
- Add babel injection grammar so <script type="text/babel"> blocks highlight as JS
- Configure monaco.languages.html.htmlDefaults and javascriptDefaults for richer
  IntelliSense inside embedded script/style regions
- Add editor settings panel: theme (system/light/dark), font size, word wrap, minimap,
  line numbers, tab size
- Fix async theme registration race: apply Shiki theme imperatively after grammars load
- Update ConsolePanel to use CSS variable colors and --font-mono
- Fix HistoryPanel restore to use artifactSource from restored snapshot and call
  loadCommentsForCurrentDesign after restore
- Add error handling for listForProvider in Settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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] History-load failures are still swallowed and rendered as an empty history state — apps/desktop/src/renderer/src/components/HistoryPanel.tsx:33, apps/desktop/src/renderer/src/components/HistoryPanel.tsx:42, apps/desktop/src/renderer/src/components/HistoryPanel.tsx:105. The new History view still catches window.codesign.snapshots.list() with an empty handler, clears loading in finally, and then falls through to the No history yet. branch. If the snapshots IPC or DB read fails, users lose access to history and get misleading UI instead of an actionable error.
    Suggested fix:

    const [loadError, setLoadError] = useState<string | null>(null);
    
    window.codesign.snapshots
      .list(currentDesignId)
      .then((rows) => {
        setLoadError(null);
        setSnapshots(rows);
        setSelected(rows[0] ?? null);
      })
      .catch((err) => {
        const message = err instanceof Error ? err.message : String(err);
        setLoadError(message);
        pushToast({
          variant: 'error',
          title: 'Failed to load history',
          description: message,
        });
      })
      .finally(() => setLoading(false));
  • [Minor] Monaco/TextMate bootstrap failures are still unhandled — apps/desktop/src/renderer/src/components/PreviewPane.tsx:922, apps/desktop/src/renderer/src/lib/setupMonacoTextmate.ts:35. If one of the lazy imports or createHighlighter() fails, the rejection only lands in the console; the Code tab never surfaces why editor setup failed, which violates the repo's no-silent-fallback rule.
    Suggested fix:

    const pushToast = useCodesignStore((s) => s.pushToast);
    
    setupTextmateGrammars(monaco)
      .then(() => {
        monaco.editor.setTheme(resolvedThemeRef.current);
      })
      .catch((err) => {
        pushToast({
          variant: 'error',
          title: 'Editor setup failed',
          description: err instanceof Error ? err.message : String(err),
        });
      });

Summary

  • Review mode: follow-up after new commits. Two issues remain on the current head: the History tab still hides snapshot-load failures behind an empty-state fallback, and the new Monaco/Shiki bootstrap path still has an unreported failure mode.
  • Residual risk: I did not find coverage for the history-load error path or for Monaco/TextMate bootstrap failures.

Testing

  • Not run (automation)

open-codesign Bot

setSnapshots(rows);
setSelected(rows[0] ?? null);
})
.catch(() => {})
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.

This still swallows snapshots.list() failures and then falls through to the No history yet. empty state once finally clears loading. That is both a misleading UX regression and a direct violation of the repo's no-silent-fallback rule.

Suggested fix:

const [loadError, setLoadError] = useState<string | null>(null);

window.codesign.snapshots
  .list(currentDesignId)
  .then((rows) => {
    setLoadError(null);
    setSnapshots(rows);
    setSelected(rows[0] ?? null);
  })
  .catch((err) => {
    const message = err instanceof Error ? err.message : String(err);
    setLoadError(message);
    pushToast({
      variant: 'error',
      title: 'Failed to load history',
      description: message,
    });
  })
  .finally(() => setLoading(false));

});

monacoRef.current = monaco;
setupTextmateGrammars(monaco).then(() => {
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.

setupTextmateGrammars() is now on the critical path for the Code tab, but this call still has no failure handling. If the lazy Shiki/Monaco import or createHighlighter() rejects, the user only gets a console rejection and no surfaced error, which conflicts with the repo's no-silent-fallback rule.

Suggested fix:

const pushToast = useCodesignStore((s) => s.pushToast);

setupTextmateGrammars(monaco)
  .then(() => {
    monaco.editor.setTheme(resolvedThemeRef.current);
  })
  .catch((err) => {
    pushToast({
      variant: 'error',
      title: 'Editor setup failed',
      description: err instanceof Error ? err.message : String(err),
    });
  });

@hqhq1025 hqhq1025 added bot-rerun Temporary label to rerun automation and removed bot-rerun Temporary label to rerun automation labels May 1, 2026
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.

Review mode: initial

Findings

[Major] pi-ai downgrade from ^0.70.2 to ^0.67.68 without explanation

packages/core/package.json and packages/providers/package.json both downgrade the @mariozechner/pi-ai and @mariozechner/pi-agent-core dependencies from ^0.70.2 to ^0.67.68. The lockfile resolves to 0.67.68. This is a significant semantic version change (72 → 68) that may introduce API incompatibility or miss critical bug fixes. No commit message or PR description explains why. If this was intentional (e.g., due to a breaking change in 0.70.x), it should be documented.

Suggested fix:

  • Add a comment in the PR body or a separate changeset explaining the downgrade
  • If a revert is intended, restore the ^0.70.2 range

[Major] macOS window controls missing traffic lights; potential crash in WindowControls

The PR changes the window to frame: false and removes titleBarStyle: 'hiddenInset' for macOS (apps/desktop/src/main/index.ts). This removes the native traffic light buttons (close/minimize/maximize) entirely. The custom WindowControls component attempts to detect macOS via (window as unknown as { process: { platform: string } }).process (apps/desktop/src/renderer/src/components/WindowControls.tsx:34), but in an Electron renderer with contextIsolation: true (default since Electron 12), window.process is undefined. This will cause a runtime error when the component mounts, breaking window controls on all platforms.

Suggested fix:

  • Use the preload API to expose the platform instead of reading from window.process
  • On macOS, keep titleBarStyle: 'hiddenInset' with frame: false? or use a conditional that preserves native traffic lights
  • Alternatively, drop the process access and always render custom buttons (but note macOS expectations)

[Major] Missing changeset for new features

The PR adds substantial new UI features (tabbed canvas with History/Code tabs, Monaco editor, custom window chrome, model routing settings, etc.) but only includes a single changeset persist-fast-model-v3.md covering the config persistence fix. By project convention (CLAUDE.md: "Added a changeset (pnpm changeset) if user-visible"), these UX changes should have their own changeset.

Suggested fix:

  • Run pnpm changeset and add descriptions for the new features (at least one changeset for the desktop UX changes)

[Minor] Dependency specifier downgrades without reason

apps/desktop/package.json downgrades @tailwindcss/postcss from ^4.2.4 to ^4.0.0, tailwindcss from ^4.2.4 to ^4.0.0, and electron from ^39.8.9 to ^39.8.8. The lockfile still resolves to 4.2.4 and 39.8.9 respectively because of caret ranges, but the looser specifier may cause future installs to pull older minor versions. No rationale provided.

Suggested fix:

  • Revert specifier downgrades unless there is a specific compatibility requirement

[Minor] Empty catch blocks in HistoryPanel

apps/desktop/src/renderer/src/components/HistoryPanel.tsx:44 swallows the snapshot listing error silently (catch(() => {})). While not critical, it makes debugging harder.

Suggested fix:

  • At minimum log the error using console.warn or useCodesignStore's error reporting

[Nit] Hardcoded ENABLE_MONACO_CODE_EDITOR = true

In apps/desktop/src/renderer/src/components/PreviewPane.tsx:58, the Monaco code editor is always enabled. Consider making this a configurable feature flag (e.g., environment variable) to allow users to disable it for bundle size savings.

Questions

  • What is the reason for the pi-ai downgrade? Are there known compatibility issues with ^0.70.2?
  • On macOS, how do users close/minimize/maximize the window without traffic light buttons or a menu bar? Is this intentional?

Summary

This is a large PR (55 files changed) that fixes a config persistence bug, adds tabbed canvas (Files/History/Code), a Monaco-based code editor, custom window chrome, model routing settings, console forwarding, and many UX refinements. The code quality is generally high with good test coverage for new utilities. However, there are three Major issues: an unexplained pi-ai version downgrade, broken window controls on macOS due to an insecure process access, and missing changesets for user-visible features. The macOS window regression is a blocker for users on that platform. The downgrade and changeset issues should be addressed before merging.

Testing

  • Unit tests added for mergeActiveModelIfMissing, decideCodeDraftSync, preservedV3OptionalsForWrite
  • Existing store tests updated for new activeGenerations state shape
  • No Playwright E2E tests added for visual changes (tabbed canvas, Monaco editor, window controls) — recommended for future iterations

Open-CoDesign Bot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:build Turbo/Vite/Biome/tsconfig toolchain area:core packages/core (generation orchestration) area:desktop apps/desktop (Electron shell, renderer) area:providers packages/providers (pi-ai adapter, model calls) area:ui packages/ui (design system) docs Documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants