Skip to content

feat(desktop): surface language toggle#20

Merged
Sun-sunshine06 merged 2 commits intomainfrom
feat/desktop-language-toggle
Apr 18, 2026
Merged

feat(desktop): surface language toggle#20
Sun-sunshine06 merged 2 commits intomainfrom
feat/desktop-language-toggle

Conversation

@Sun-sunshine06
Copy link
Copy Markdown
Collaborator

Summary

  • initialize desktop i18n from the persisted locale before the renderer mounts
  • expose a visible language toggle in both the top bar and onboarding flow
  • localize shell copy that previously stayed hard-coded in English

Testing

  • corepack pnpm --filter @open-codesign/desktop typecheck

Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.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] Wiring registerLocaleIpc() makes locale persistence live, but that implementation currently writes to a hardcoded ~/.config/open-codesign/locale.json path. The repo explicitly says not to hard-code paths and to respect XDG / Electron path helpers, so this will diverge from non-default config roots. Evidence apps/desktop/src/main/index.ts:258, context apps/desktop/src/main/locale-ipc.ts:19.
    Suggested fix:

    import { join } from 'node:path';
    import { app } from './electron-runtime';
    
    const LOCALE_FILE = join(app.getPath('userData'), 'locale.json');
  • [Major] The new locale IPC contract is unversioned (locale:get-system, locale:get-current, locale:set). docs/PRINCIPLES.md §5b requires every IPC channel to be versioned so future contract changes can ship side-by-side without breaking older renderers. Evidence apps/desktop/src/preload/index.ts:56.
    Suggested fix:

    const LOCALE_GET_SYSTEM = 'locale:v1:get-system';
    const LOCALE_GET_CURRENT = 'locale:v1:get-current';
    const LOCALE_SET = 'locale:v1:set';
  • [Minor] LanguageToggle introduces a hardcoded text-[12px] literal. UI values are supposed to come from packages/ui tokens, so this adds a new §8 violation on a freshly added component. Evidence apps/desktop/src/renderer/src/components/LanguageToggle.tsx:33.
    Suggested fix:

    className="... text-[var(--text-xs)] ..."
  • [Minor] The newly modified top bar still hardcodes English tooltip/button copy, so switching to zh-CN leaves part of the shell untranslated even though this PR is specifically surfacing language switching. Evidence apps/desktop/src/renderer/src/components/TopBar.tsx:39.
    Suggested fix:

    <Tooltip label={t('commands.tooltips.commandPalette')}>
      <IconButton label={t('commands.items.openCommandPalette')} size="sm" onClick={openCommandPalette}>

Summary

  • Review mode: initial. Four issues found: one path-handling regression activated by the new locale IPC wiring, one compatibility violation in the new IPC surface, and two renderer-level consistency issues in the new language toggle/top-bar UI.

Testing

  • Not run (automation): this runner has no installed node_modules, so corepack pnpm --filter @open-codesign/desktop typecheck and corepack pnpm --filter @open-codesign/i18n test both failed before execution. No new Vitest/Playwright coverage was added for locale persistence, bootstrap, or translated shell copy.

open-codesign Bot

initLogger();
await loadConfigOnBoot();
registerIpcHandlers();
registerLocaleIpc();
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.

Activating registerLocaleIpc() makes locale persistence live, but that implementation currently writes to a hardcoded ~/.config/open-codesign/locale.json path (apps/desktop/src/main/locale-ipc.ts:19). The repo explicitly says not to hard-code paths and to respect XDG / Electron path helpers. Can this be moved under app.getPath("userData") or the same config-dir helper used elsewhere?

Suggested fix:

import { join } from 'node:path';
import { app } from './electron-runtime';

const LOCALE_FILE = join(app.getPath('userData'), 'locale.json');

export: (payload: { format: ExportFormat; htmlContent: string; defaultFilename?: string }) =>
ipcRenderer.invoke('codesign:export', payload) as Promise<ExportInvokeResponse>,
locale: {
getSystem: () => ipcRenderer.invoke('locale:get-system') as Promise<string>,
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.

These new locale IPC channels are unversioned (locale:get-system, locale:get-current, locale:set). docs/PRINCIPLES.md §5b requires every IPC channel to be versioned so protocol changes can ship side-by-side without breaking older renderers.

Suggested fix:

const LOCALE_GET_SYSTEM = 'locale:v1:get-system';
const LOCALE_GET_CURRENT = 'locale:v1:get-current';
const LOCALE_SET = 'locale:v1:set';

<button
type="button"
onClick={() => void handleToggle()}
className="inline-flex items-center gap-2 h-8 px-3 rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-surface)] text-[12px] text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors"
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.

text-[12px] introduces a new hardcoded UI literal on a brand-new component. The repo rule is that visual values come from packages/ui tokens, so this should use an existing token or add one first.

Suggested fix:

className="... text-[var(--text-xs)] ..."


<div className="flex items-center gap-1" style={noDragStyle}>
<Tooltip label="Command palette K">
<Tooltip label="Command palette Ctrl/Cmd+K">
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 newly modified top-bar copy is still hardcoded in English, so switching to zh-CN leaves part of the shell untranslated even though this PR is about surfacing language switching. Can these strings move behind translation keys as well?

Suggested fix:

<Tooltip label={t('commands.tooltips.commandPalette')}>
  <IconButton label={t('commands.items.openCommandPalette')} size="sm" onClick={openCommandPalette}>

@Sun-sunshine06 Sun-sunshine06 force-pushed the feat/desktop-language-toggle branch from 0fdd707 to ed6e073 Compare April 18, 2026 12:02
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] Locale persistence is now live, but the registered handler still writes to a hardcoded ~/.config/open-codesign/locale.json path. That bypasses Electron/XDG path helpers and directly violates the repo rule against hardcoded paths. Evidence apps/desktop/src/main/index.ts:258, context apps/desktop/src/main/locale-ipc.ts:19.
    Suggested fix:

    import { join } from 'node:path';
    import { app } from './electron-runtime';
    
    const LOCALE_FILE = join(app.getPath('userData'), 'locale.json');
  • [Major] The new locale IPC surface is unversioned (locale:get-system, locale:get-current, locale:set). docs/PRINCIPLES.md §5b makes IPC versioning non-optional, so shipping these names as-is creates a compatibility contract we cannot evolve safely. Evidence apps/desktop/src/preload/index.ts:56.
    Suggested fix:

    const LOCALE_GET_SYSTEM = 'locale:v1:get-system';
    const LOCALE_GET_CURRENT = 'locale:v1:get-current';
    const LOCALE_SET = 'locale:v1:set';
  • [Major] The renderer now silently degrades when the preload bridge is missing: bootstrap falls back to the default locale and the toggle applies an in-memory change without persisting it. That violates §10's “no silent fallbacks” rule and turns a broken IPC bridge into a hard-to-debug “language reverted after restart” bug. Evidence apps/desktop/src/renderer/src/main.tsx:12, apps/desktop/src/renderer/src/components/LanguageToggle.tsx:24.
    Suggested fix:

    if (!window.codesign) {
      throw new Error('Locale bridge unavailable');
    }
    
    const locale = await window.codesign.locale.getCurrent();
  • [Major] Surfacing LanguageToggle in onboarding exposes a language switch on a flow whose visible copy is still almost entirely hard-coded English (Welcome, PasteKey, ChooseModel). In zh-CN, users still see “Design with any model”, “Paste your API key”, “Pick default models”, etc., so the new onboarding toggle does not actually localize the screen it was added to. Evidence apps/desktop/src/renderer/src/onboarding/index.tsx:70, context apps/desktop/src/renderer/src/onboarding/Welcome.tsx:15, apps/desktop/src/renderer/src/onboarding/PasteKey.tsx:142, apps/desktop/src/renderer/src/onboarding/ChooseModel.tsx:50.
    Suggested fix:

    const t = useT();
    
    <h1>{t('onboarding.welcome.title')}</h1>
    <p>{t('onboarding.welcome.subtitle')}</p>

Summary

  • Review mode: follow-up after new commits. Four issues found in the newly wired locale/onboarding surfaces: one path-handling regression, one IPC compatibility violation, one silent-fallback regression, and one onboarding-localization gap.

Testing

  • Not run (automation): corepack pnpm --filter @open-codesign/desktop typecheck failed because node_modules are absent and the runner is on Node v20.20.2 while the repo requires >=22; corepack pnpm --filter @open-codesign/i18n test failed because vitest is unavailable without dependencies installed. No new Vitest/Playwright coverage was added for locale bootstrap, persistence, or the onboarding toggle.

open-codesign Bot

initLogger();
await loadConfigOnBoot();
registerIpcHandlers();
registerLocaleIpc();
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.

Activating registerLocaleIpc() makes locale persistence live, but locale-ipc.ts still writes to a hardcoded ~/.config/open-codesign/locale.json path (apps/desktop/src/main/locale-ipc.ts:19). CLAUDE.md explicitly forbids hardcoded paths. Can this move under app.getPath("userData") or the same config-dir helper used elsewhere?

Suggested fix:

import { join } from 'node:path';
import { app } from './electron-runtime';

const LOCALE_FILE = join(app.getPath('userData'), 'locale.json');

export: (payload: { format: ExportFormat; htmlContent: string; defaultFilename?: string }) =>
ipcRenderer.invoke('codesign:export', payload) as Promise<ExportInvokeResponse>,
locale: {
getSystem: () => ipcRenderer.invoke('locale:get-system') as Promise<string>,
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.

These new locale IPC channels are unversioned. docs/PRINCIPLES.md §5b requires every IPC contract to ship as vN so future renderer/main mismatches can coexist safely.

Suggested fix:

const LOCALE_GET_SYSTEM = 'locale:v1:get-system';
const LOCALE_GET_CURRENT = 'locale:v1:get-current';
const LOCALE_SET = 'locale:v1:set';

</StrictMode>,
);
async function bootstrap(): Promise<void> {
const locale = window.codesign ? await window.codesign.locale.getCurrent() : undefined;
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 boot path silently falls back when the preload bridge is missing. That means a broken locale IPC surface boots in the default language instead of failing loudly, and LanguageToggle has the same pattern when persisting. docs/PRINCIPLES.md §10 bans this kind of masked fallback.

Suggested fix:

if (!window.codesign) {
  throw new Error('Locale bridge unavailable');
}

const locale = await window.codesign.locale.getCurrent();

<Wordmark badge="pre-alpha" />
<Stepper current={idx} total={3} />
<div className="flex items-center gap-2">
<LanguageToggle />
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.

Adding LanguageToggle here makes onboarding advertise language switching, but the actual onboarding copy is still hard-coded English in Welcome.tsx, PasteKey.tsx, and ChooseModel.tsx. In zh-CN, the user still sees "Design with any model", "Paste your API key", etc., so this new surface does not localize the screen it was added to.

Suggested fix:

const t = useT();

<h1>{t('onboarding.welcome.title')}</h1>
<p>{t('onboarding.welcome.subtitle')}</p>

Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.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] Locale persistence still writes to a hardcoded ~/.config/open-codesign/locale.json path once this PR activates registerLocaleIpc(). That bypasses Electron/XDG path helpers and violates the repo rule against hardcoded paths. Evidence apps/desktop/src/main/index.ts:259, context apps/desktop/src/main/locale-ipc.ts:19.
    Suggested fix:

    import { join } from 'node:path';
    import { app } from './electron-runtime';
    
    const LOCALE_FILE = join(app.getPath('userData'), 'locale.json');
  • [Major] The new locale bridge is still exposing unversioned IPC channels (locale:get-system, locale:get-current, locale:set). docs/PRINCIPLES.md §5b makes IPC versioning non-optional, so this ships a compatibility contract we cannot evolve safely. Evidence apps/desktop/src/preload/index.ts:56.
    Suggested fix:

    const LOCALE_GET_SYSTEM = 'locale:v1:get-system';
    const LOCALE_GET_CURRENT = 'locale:v1:get-current';
    const LOCALE_SET = 'locale:v1:set';
  • [Major] Locale bootstrap/toggling still silently falls back when the preload bridge is missing. main.tsx boots with undefined, and LanguageToggle applies the locale in memory without persistence, which violates PRINCIPLES §10 and makes a broken preload surface look like “language reverted after restart”. Evidence apps/desktop/src/renderer/src/main.tsx:12, apps/desktop/src/renderer/src/components/LanguageToggle.tsx:24.
    Suggested fix:

    if (!window.codesign) {
      throw new Error('Locale bridge unavailable');
    }
    
    const locale = await window.codesign.locale.getCurrent();
    const persisted = await window.codesign.locale.set(target);
  • [Major] Onboarding now advertises a language switch, but the onboarding screens still render hard-coded English even though onboarding.* translations already exist in the locale bundles. In zh-CN, switching language still leaves Welcome, Paste your API key, and Pick default models untranslated, so the new surface does not localize the flow it was added to. Evidence apps/desktop/src/renderer/src/onboarding/index.tsx:70, context apps/desktop/src/renderer/src/onboarding/Welcome.tsx:14, apps/desktop/src/renderer/src/onboarding/PasteKey.tsx:141, apps/desktop/src/renderer/src/onboarding/ChooseModel.tsx:49, packages/i18n/src/locales/en.json:82.
    Suggested fix:

    const t = useT();
    
    <h1>{t('onboarding.welcome.title')}</h1>
    <p>{t('onboarding.welcome.subtitle')}</p>

Summary

  • Review mode: follow-up after new commits. Four issues remain in the locale/onboarding path: hardcoded locale storage, unversioned IPC, silent bridge fallbacks, and an onboarding toggle that does not actually localize the onboarding flow.

Testing

  • Not run (automation): node_modules/ is absent in this runner.
  • Existing coverage only exercises packages/i18n/src/i18n.test.ts; no Vitest/Playwright coverage was added for Electron preload IPC, renderer bootstrap from persisted locale, or the onboarding toggle.

open-codesign Bot

initLogger();
await loadConfigOnBoot();
registerIpcHandlers();
registerLocaleIpc();
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.

Activating registerLocaleIpc() makes locale persistence live, but locale-ipc.ts still writes to a hardcoded ~/.config/open-codesign/locale.json path (apps/desktop/src/main/locale-ipc.ts:19). CLAUDE.md forbids hardcoded paths. Can this move under app.getPath('userData')?

Suggested fix:

import { join } from 'node:path';
import { app } from './electron-runtime';

const LOCALE_FILE = join(app.getPath('userData'), 'locale.json');

export: (payload: { format: ExportFormat; htmlContent: string; defaultFilename?: string }) =>
ipcRenderer.invoke('codesign:export', payload) as Promise<ExportInvokeResponse>,
locale: {
getSystem: () => ipcRenderer.invoke('locale:get-system') as Promise<string>,
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.

These locale IPC channels are still unversioned. docs/PRINCIPLES.md §5b requires every IPC contract to ship as vN so future renderer/main mismatches can coexist safely.

Suggested fix:

const LOCALE_GET_SYSTEM = 'locale:v1:get-system';
const LOCALE_GET_CURRENT = 'locale:v1:get-current';
const LOCALE_SET = 'locale:v1:set';

</StrictMode>,
);
async function bootstrap(): Promise<void> {
const locale = window.codesign ? await window.codesign.locale.getCurrent() : undefined;
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 boot path still silently falls back when the preload bridge is missing, and LanguageToggle keeps the same pattern when persisting (apps/desktop/src/renderer/src/components/LanguageToggle.tsx:24). That turns a broken locale bridge into a quiet “language reverted after restart” bug.

Suggested fix:

if (!window.codesign) {
  throw new Error('Locale bridge unavailable');
}

const locale = await window.codesign.locale.getCurrent();
const persisted = await window.codesign.locale.set(target);

<Wordmark badge="pre-alpha" />
<Stepper current={idx} total={3} />
<div className="flex items-center gap-2">
<LanguageToggle />
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.

Adding LanguageToggle here advertises locale switching, but the actual onboarding copy is still hard-coded English in Welcome.tsx, PasteKey.tsx, and ChooseModel.tsx, even though packages/i18n/src/locales/en.json / zh-CN.json already define onboarding.* keys. In zh-CN, this flow still renders English.

Suggested fix:

const t = useT();

<h1>{t('onboarding.welcome.title')}</h1>
<p>{t('onboarding.welcome.subtitle')}</p>

@Sun-sunshine06 Sun-sunshine06 merged commit 755d610 into main Apr 18, 2026
7 checks passed
@Sun-sunshine06 Sun-sunshine06 deleted the feat/desktop-language-toggle branch April 18, 2026 12:38
hqhq1025 added a commit that referenced this pull request Apr 18, 2026
…in features

Rebase onto origin/main exposed conflicts where PRs #18, #19, #20 had
advanced store, preload, shared and main/index.ts beyond the branch
point. Resolve by taking origin/main as the base and layering our
cancellation additions on top:

- shared/index.ts: add LocalInputFile, SelectedElement, ElementSelectionRect,
  ApplyCommentPayload, StoredDesignSystem exports; add generationId to
  GeneratePayload; keep CancelGenerationPayloadV1
- preload/index.ts: add locale API, pickInputFiles, pickDesignSystemDirectory,
  clearDesignSystem, applyComment; add generationId to generate payload;
  keep cancelGeneration with schemaVersion: 1
- main/index.ts: add applyComment, scanDesignSystem, preparePromptContext,
  registerLocaleIpc; wire AbortController into generate handler; keep
  codesign:v1:cancel-generation handler
- store.ts: merge all origin/main state (selectedElement, inputFiles,
  referenceUrl, i18n tr(), applyInlineComment, etc.) with PR's
  activeGenerationId / cancelGeneration / finishIfCurrent guards
- store.test.ts: update sendPrompt calls to object form; add designSystem:
  null to OnboardingState fixture; relax toast title assertion for i18n

Signed-off-by: hqhq1025 <1506751656@qq.com>
hqhq1025 added a commit that referenced this pull request Apr 18, 2026
…in features

Rebase onto origin/main exposed conflicts where PRs #18, #19, #20 had
advanced store, preload, shared and main/index.ts beyond the branch
point. Resolve by taking origin/main as the base and layering our
cancellation additions on top:

- shared/index.ts: add LocalInputFile, SelectedElement, ElementSelectionRect,
  ApplyCommentPayload, StoredDesignSystem exports; add generationId to
  GeneratePayload; keep CancelGenerationPayloadV1
- preload/index.ts: add locale API, pickInputFiles, pickDesignSystemDirectory,
  clearDesignSystem, applyComment; add generationId to generate payload;
  keep cancelGeneration with schemaVersion: 1
- main/index.ts: add applyComment, scanDesignSystem, preparePromptContext,
  registerLocaleIpc; wire AbortController into generate handler; keep
  codesign:v1:cancel-generation handler
- store.ts: merge all origin/main state (selectedElement, inputFiles,
  referenceUrl, i18n tr(), applyInlineComment, etc.) with PR's
  activeGenerationId / cancelGeneration / finishIfCurrent guards
- store.test.ts: update sendPrompt calls to object form; add designSystem:
  null to OnboardingState fixture; relax toast title assertion for i18n

Signed-off-by: hqhq1025 <1506751656@qq.com>
hqhq1025 added a commit that referenced this pull request Apr 18, 2026
* feat(desktop): redesign sidebar input + add cancellation IPC

- Replace single-line input with autosize textarea (1–6 rows, max-h 144 px)
- Move send button into bottom-right corner of textarea container, icon-only 28 px
- During generation, send button becomes a stop button (Square icon) wired to cancelGeneration
- Remove redundant keyboard-hint bar; fold hint into placeholder text
- Enter sends, Shift+Enter inserts newline, ⌘↵ global send preserved
- main: extract runGenerate helper; maintain Map<id, AbortController> for in-flight
  requests; new codesign:cancel-generation IPC handler; logger scope 'ipc'
- preload: expose cancelGeneration(id) on window.codesign
- shared: GeneratePayload accepts optional generationId
- store: add activeGenerationId state + cancelGeneration action; suppress abort
  errors silently without pushing an error toast

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix: harden generation cancellation flows

Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com>
Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix: version cancellation ipc and tokenise sidebar

Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com>
Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix: preserve current-generation failures

Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com>
Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix(desktop): guard cancel race + version IPC payload + add tests

- applyGenerateSuccess/applyGenerateError already guarded via
  finishIfCurrent and the early-return id check; confirm with tests
- Add noop test: unknown generationId leaves all in-flight intact
- Add schema rejection test: empty generationId and missing
  schemaVersion both throw via CancelGenerationPayloadV1.parse
- Fix biome formatting in store.test.ts (pre-existing)

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix(desktop): properly merge rebase onto main — restore all origin/main features

Rebase onto origin/main exposed conflicts where PRs #18, #19, #20 had
advanced store, preload, shared and main/index.ts beyond the branch
point. Resolve by taking origin/main as the base and layering our
cancellation additions on top:

- shared/index.ts: add LocalInputFile, SelectedElement, ElementSelectionRect,
  ApplyCommentPayload, StoredDesignSystem exports; add generationId to
  GeneratePayload; keep CancelGenerationPayloadV1
- preload/index.ts: add locale API, pickInputFiles, pickDesignSystemDirectory,
  clearDesignSystem, applyComment; add generationId to generate payload;
  keep cancelGeneration with schemaVersion: 1
- main/index.ts: add applyComment, scanDesignSystem, preparePromptContext,
  registerLocaleIpc; wire AbortController into generate handler; keep
  codesign:v1:cancel-generation handler
- store.ts: merge all origin/main state (selectedElement, inputFiles,
  referenceUrl, i18n tr(), applyInlineComment, etc.) with PR's
  activeGenerationId / cancelGeneration / finishIfCurrent guards
- store.test.ts: update sendPrompt calls to object form; add designSystem:
  null to OnboardingState fixture; relax toast title assertion for i18n

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix(desktop): wait for cancel IPC ack + version generate payload

Major #1: cancelGeneration now waits for the IPC round-trip before
clearing isGenerating/activeGenerationId. If main rejects the cancel
(bad id, already aborted) the error becomes visible via toast instead
of being silently swallowed. Updated store test to reflect async cancel
and drain microtasks between cancel and the follow-up sendPrompt.

Major #2: add GeneratePayloadV1 (schemaVersion: 1, generationId required)
to @open-codesign/shared and rename the IPC channel to codesign:v1:generate,
matching the pattern established by codesign:v1:cancel-generation. Preload
builds the v1 envelope with satisfies check; main validates with
GeneratePayloadV1.parse(). Add 5 schema tests for missing/wrong schemaVersion
and empty generationId. Add notifications.cancellationFailed i18n key.

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix(desktop): restore legacy generate IPC shim + use ui tokens in sidebar

- Add codesign:generate legacy handler that accepts old GeneratePayload
  (no schemaVersion) and promotes it to V1 by injecting schemaVersion:1
  and falling back generationId to gen-${Date.now()} when absent.
  Emits a warn log to track removal in the next minor release.
- Add --color-on-accent token to packages/ui tokens.css (light + dark)
  so accent-button text never hard-codes a color value.
- Replace text-white in Sidebar.tsx with text-[var(--color-on-accent)]
  on both the stop and send IconButton variants.
- Add 3 tests for GeneratePayload legacy schema and the V1 promotion
  path that the shim relies on.

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix(desktop): surface error when renderer bridge missing during cancel

Split the combined `!id || !window.codesign` guard in `cancelGeneration`
into two separate checks. A missing `activeGenerationId` is a legitimate
no-op, but a missing IPC bridge is a real error that must be surfaced via
`errorMessage`/`lastError` and a toast. Adds a Vitest test to cover the
new error path (with `beforeAll` i18n init so `tr()` keys resolve).

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix(desktop): restore attachments/refURL/designSystem entries + i18n sidebar strings

Restores the local context panel (attachments, reference URL, design
system) that was accidentally dropped during rebase when the cancel
button was added.  All hardcoded English strings are now routed through
useT(); chat.send and chat.stop keys were added to en.json / zh-CN.json.

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix(desktop): fix import order and trailing blank line after rebase

Biome import sort and formatter fixes introduced during rebase conflict
resolution.

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix(desktop): remove hardcoded textarea fallback, throw on missing tokens

Removes FALLBACK_FONT_SIZE and FALLBACK_LINE_HEIGHT_MULTIPLIER constants
from getTextareaLineHeight. When computed lineHeight is not a finite
positive number and either fontSize or --leading-body token is absent or
invalid, the function now throws instead of silently using magic numbers.
The --leading-body token is already defined in packages/ui/src/tokens.css.

Updates Sidebar.test.ts: replaces the "falls back" test with two tests —
one verifying valid-token multiplication and one verifying the throw path.

Signed-off-by: hqhq1025 <1506751656@qq.com>

---------

Signed-off-by: hqhq1025 <1506751656@qq.com>
Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com>
Co-authored-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com>
hqhq1025 added a commit that referenced this pull request Apr 18, 2026
The PR commits were originally based on an older version of the codebase
and accidentally removed features added by PRs #18 and #20:

- Restore design-system.ts and prompt-context.ts (deleted in branch)
- Restore InlineCommentComposer.tsx and PreviewPane.test.ts (deleted)
- Restore StoredDesignSystem / designSystem support in config.ts
- Re-export StoredDesignSystem + STORED_DESIGN_SYSTEM_SCHEMA_VERSION from shared
- Re-add getOnboardingState() / setDesignSystem() to onboarding-ipc.ts
  (these are needed by main/index.ts design-system IPC handlers)
- Add full import set to main/index.ts (stat, basename, applyComment,
  dialog, scanDesignSystem, getCachedConfig, getOnboardingState,
  setDesignSystem, preparePromptContext)
- Restore preload/index.ts: add back applyComment, pickInputFiles,
  pickDesignSystemDirectory, clearDesignSystem methods removed in PR;
  keep PR-added settings/preferences namespaces and interfaces
- Restore all other files regressed to pre-#18/#20 state (Sidebar.tsx,
  store.ts, App.tsx, core/index.ts, providers, templates, ui tokens, etc.)
- Fix Settings.tsx formatting (spurious blank line)
- Fix store.test.ts READY_CONFIG missing designSystem field
hqhq1025 added a commit that referenced this pull request Apr 18, 2026
…#16)

* feat(desktop): settings tabs with multi-provider management and preferences persistence

Implements all four settings tabs (Models, Appearance, Storage, Advanced) with
full persistence and correct error handling.

Key changes:
- Settings UI: four-tab panel (Models / Appearance / Storage / Advanced) with
  provider cards, add/delete/activate flows, model selector, path viewer, reset
- toProviderRows: soft-fail on safeStorage decryption — returns error:'decryption_failed'
  row instead of throwing; UI shows red badge + 'Re-enter key' button
- preferences-ipc.ts: new IPC module persisting updateChannel and generationTimeoutSec
  to ~/.config/open-codesign/preferences.json (schemaVersion:1)
- locale-ipc.ts and preferences-ipc.ts registered in main/index.ts
- preload: settings, preferences, and locale namespaces exposed to renderer
- AppearanceTab: language select reads/writes via locale:get-current / locale:set
- AdvancedTab: update channel and generation timeout read/write via preferences IPC
- ActiveModelSelector: 400ms debounce with proper useEffect cleanup on unmount
- delete-provider: fix ghost active provider — when all providers removed, write
  tombstone config (empty secrets) instead of falling back to hardcoded 'openai';
  re-adding a new provider correctly auto-activates it
- provider-settings.ts and onboarding-ipc.ts: use electron-runtime import pattern
- Tests: 8 total (5 new), covering decryption-failed row, masked-key row, ghost-provider
  auto-activate, provider preservation, assertProviderHasStoredSecret

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix(desktop): switch model defaults on provider delete + guard validate against stale form

- Extract computeDeleteProviderResult pure helper to provider-settings.ts;
  when the active provider is deleted the new active provider's PROVIDER_SHORTLIST
  defaults are used for modelPrimary/modelFast instead of carrying over the old
  provider's model IDs (Blocker from #16 review).
- Tombstone path (last provider removed) now writes empty model strings so a
  stale model ID is never persisted.
- Guard handleValidate in AddProviderModal: snapshot provider/apiKey/baseUrl
  before the async call and discard the result via applyValidateResult if the
  form changed while awaiting (Major race fix from #16 review).
- Add applyValidateResult pure exported helper for unit testing without a DOM.
- 5 new tests (3 computeDeleteProviderResult + 4 applyValidateResult, total 15).

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix(desktop): version settings/preferences IPC + ui token compliance

- Add settings:v1:* and preferences:v1:* versioned IPC channels (8 settings + 2 prefs)
- Keep legacy settings:* and preferences:* channels as shims with logger.warn
- Preload now calls v1 channels exclusively
- Extract per-handler run* helpers shared by v1 and legacy registrations
- Replace text-white with text-[var(--color-on-accent)] in Settings badges/buttons
- Replace text-[10px] with text-[var(--font-size-badge)] in ProviderCard badges
- Add --color-on-accent and --font-size-badge tokens to packages/ui/src/tokens.css
- Add onboarding-ipc.test.ts: 3 tests covering v1 channel registration and legacy compat

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix: surface silent failures in locale/preferences/generate paths

- Settings.tsx locale-load .catch(() => {}) → pushToast error
- Settings.tsx preferences bootstrap .catch(() => {}) → pushToast error
- preferences-ipc.ts non-ENOENT read errors → throw CodesignError (PREFERENCES_READ_FAILED)
- packages/core generate() empty artifacts → throw CodesignError (OUTPUT_MISSING_ARTIFACT)
- Add pushIframeError to CodesignState interface (was implemented but missing from type)
- Fix store.test.ts: remove stale designSystem field not present in OnboardingState
- Add preferences-ipc.test.ts: 2 tests covering ENOENT default and non-ENOENT throw
- Add generate.test.ts: 4 tests covering OUTPUT_MISSING_ARTIFACT error path

Signed-off-by: hqhq1025 <1506751656@qq.com>

* fix(desktop): incorporate i18n dep and shared type additions from main

Add @open-codesign/i18n workspace dep, LocalInputFile/ElementSelection
types from main, and fix sendPrompt call signature in App.tsx to match
the object-input type declared in store.ts.

* fix(desktop): restore main-branch features removed during rebase

The PR commits were originally based on an older version of the codebase
and accidentally removed features added by PRs #18 and #20:

- Restore design-system.ts and prompt-context.ts (deleted in branch)
- Restore InlineCommentComposer.tsx and PreviewPane.test.ts (deleted)
- Restore StoredDesignSystem / designSystem support in config.ts
- Re-export StoredDesignSystem + STORED_DESIGN_SYSTEM_SCHEMA_VERSION from shared
- Re-add getOnboardingState() / setDesignSystem() to onboarding-ipc.ts
  (these are needed by main/index.ts design-system IPC handlers)
- Add full import set to main/index.ts (stat, basename, applyComment,
  dialog, scanDesignSystem, getCachedConfig, getOnboardingState,
  setDesignSystem, preparePromptContext)
- Restore preload/index.ts: add back applyComment, pickInputFiles,
  pickDesignSystemDirectory, clearDesignSystem methods removed in PR;
  keep PR-added settings/preferences namespaces and interfaces
- Restore all other files regressed to pre-#18/#20 state (Sidebar.tsx,
  store.ts, App.tsx, core/index.ts, providers, templates, ui tokens, etc.)
- Fix Settings.tsx formatting (spurious blank line)
- Fix store.test.ts READY_CONFIG missing designSystem field

---------

Signed-off-by: hqhq1025 <1506751656@qq.com>
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