Skip to content

refactor(frontend): migrate desktop app to solid#30

Merged
markbang merged 1 commit intomainfrom
refactor/use_solid
Mar 9, 2026
Merged

refactor(frontend): migrate desktop app to solid#30
markbang merged 1 commit intomainfrom
refactor/use_solid

Conversation

@markbang
Copy link
Owner

@markbang markbang commented Mar 9, 2026

Summary

  • migrate the desktop frontend from the previous React/TanStack stack to Solid + Vite
  • replace route, state, markdown preview, i18n, theme, and workspace flows with Solid implementations
  • remove the old React component tree and unused frontend dependencies
  • migrate Vitest and Playwright coverage to the Solid app and add fresh visual snapshots

Testing

  • pnpm build
  • pnpm test
  • pnpm lint
  • pnpm test:visual

Notes

  • Chromium visual snapshots are included by default
  • WebKit visual tests can be enabled locally with \undefined
     ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL  Command "test:visual" not found

Did you mean "pnpm test:visual"? after installing the required system libraries


Summary by cubic

Migrated the desktop frontend from React/Next to SolidJS with vite to simplify the stack, speed up startup, and reduce dependencies. Updated tests, Tauri config, and docs to match the new toolchain.

  • Refactors

    • Bootstrapped a Solid app via vite (index.html, src/app.tsx) and added Solid implementations for i18n, theme toggle, file browser, S3 config dialog, toast viewport, and lightweight UI in src/components/ui.tsx.
    • Removed React components, hooks, Next routing, Radix/shadcn UI, Tailwind/PostCSS, ESLint config, and legacy stores/utilities.
    • Switched tests to vitest + playwright; set baseURL to http://127.0.0.1:3000, default to Chromium, add locale/timezone; enable WebKit with PLAYWRIGHT_ENABLE_WEBKIT=1.
    • Updated Tauri to use Vite output (frontendDist: dist) and added pnpm tauri script.
    • Refreshed README badges and AGENTS guide to the new Solid/Vite structure.
  • Migration

    • Dev: pnpm dev, Build: pnpm build, Preview: pnpm preview, Lint: pnpm lint (type-check only).
    • Visual tests: pnpm test:visual (Chromium). Enable WebKit with PLAYWRIGHT_ENABLE_WEBKIT=1 pnpm test:visual.
    • Playwright runs against http://127.0.0.1:3000; web server uses pnpm dev.
    • Desktop build/run: pnpm tauri.

Written for commit 0770b08. Summary will update on new commits.

@chatgpt-codex-connector
Copy link

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@markbang markbang force-pushed the refactor/use_solid branch from 6334c17 to 0770b08 Compare March 9, 2026 07:36
@markbang markbang merged commit 1458204 into main Mar 9, 2026
9 checks passed
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

36 issues found across 156 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="README.md">

<violation number="1" location="README.md:87">
P2: Replace the placeholder clone URL with this repository's real GitHub URL; otherwise the documented setup command does not work.</violation>
</file>

<file name="src/components/i18n-provider.tsx">

<violation number="1" location="src/components/i18n-provider.tsx:26">
P2: Initialize the locale from `detectLocale()` instead of hard-coding `'en'`, otherwise the first render flashes English before switching to the user's saved/detected language.</violation>
</file>

<file name="src/components/writing-area.tsx">

<violation number="1" location="src/components/writing-area.tsx:74">
P1: Race condition: rapid file-path changes can cause a stale `readText` result to overwrite the correct content. The async IIFE has no guard to discard results that belong to a superseded effect run.

Capture a version counter (or use an `AbortController`-style flag) and check it after the `await` to discard stale reads.</violation>
</file>

<file name="test/components/theme-consistency.test.tsx">

<violation number="1" location="test/components/theme-consistency.test.tsx:7">
P2: This rewrite turns the theme-consistency suite into route smoke tests, so it no longer verifies the styling regressions the file is supposed to catch.</violation>
</file>

<file name="vite.config.ts">

<violation number="1" location="vite.config.ts:36">
P1: `esnext` is too new for Tauri's macOS/Linux webviews; use Tauri's platform-specific build targets instead.</violation>
</file>

<file name="src/components/s3-config-dialog.tsx">

<violation number="1" location="src/components/s3-config-dialog.tsx:45">
P2: Reset form/config state in the load-error path to avoid showing stale S3 data after a failed reload.</violation>
</file>

<file name="tsconfig.json">

<violation number="1" location="tsconfig.json:24">
P2: This tsconfig stops type-checking the `test/` suite and test configs, so `pnpm lint`/`pnpm build` can miss TypeScript errors outside `src/`.</violation>
</file>

<file name="src/state/workspace.ts">

<violation number="1" location="src/state/workspace.ts:36">
P1: Provide fallback values for properties parsed from local storage to prevent runtime crashes from missing keys.</violation>

<violation number="2" location="src/state/workspace.ts:90">
P2: Add a check to prevent async race conditions when loading files.</violation>
</file>

<file name="src/lib/tauri.ts">

<violation number="1" location="src/lib/tauri.ts:112">
P2: Missing `isTauri()` guard on filesystem and path operations. The rest of the module consistently checks `isTauri()` before dynamic-importing Tauri plugins, but these seven functions (`readDirectory`, `readText`, `writeText`, `readBinary`, `writeBinary`, `ensureDirectory`, `joinPaths`) skip the check. A call outside the Tauri context will produce a confusing dynamic-import error instead of the clear desktop-only message.</violation>
</file>

<file name="test/lib/utils.test.ts">

<violation number="1" location="test/lib/utils.test.ts:13">
P2: This replacement test is too weak to catch the `cn()` regression where conflicting Tailwind classes are no longer merged.</violation>
</file>

<file name="test/components/color-harmony.test.tsx">

<violation number="1" location="test/components/color-harmony.test.tsx:10">
P2: Anchor this assertion to the start of a CSS declaration; the current substring check also matches `--card-foreground` and other `*-foreground` tokens, so it can pass when `--foreground` is wrong.</violation>

<violation number="2" location="test/components/color-harmony.test.tsx:18">
P2: This dark-mode assertion needs the same declaration-line anchoring; otherwise `--card-foreground`/`--secondary-foreground` can satisfy it even if `.dark` no longer overrides `--foreground`.</violation>
</file>

<file name="src/components/theme-toggle.tsx">

<violation number="1" location="src/components/theme-toggle.tsx:9">
P2: Make `isDark` react to system color-scheme changes. In `system` mode the app theme updates on `matchMedia` events, but this memo only depends on `settings.theme`, so the toggle icon and labels can drift out of sync.</violation>
</file>

<file name="src/lib/s3.ts">

<violation number="1" location="src/lib/s3.ts:17">
P1: Normalize blank optional S3 fields before saving. The dialog submits `''` for `endpoint_url` and `path_prefix`, and Rust treats those as configured values, so leaving the endpoint blank can break every upload.</violation>
</file>

<file name="src/routes/login.tsx">

<violation number="1" location="src/routes/login.tsx:37">
P1: This `onSubmit` permanently disables the email/password login flow by preventing submission without invoking any auth logic.</violation>

<violation number="2" location="src/routes/login.tsx:39">
P2: These social sign-in buttons are inert because they have no `onClick` handler and `Button` defaults to `type="button"`.</violation>

<violation number="3" location="src/routes/login.tsx:61">
P2: These links are broken because `href="#"` only navigates to the current page hash instead of the intended destination.</violation>
</file>

<file name="test/components/interaction-states.test.tsx">

<violation number="1" location="test/components/interaction-states.test.tsx:8">
P2: This rewrite drops the hover/focus/pressed-state assertions, so interaction-style regressions now go untested.</violation>

<violation number="2" location="test/components/interaction-states.test.tsx:37">
P3: Clear the global `window.alert` mock and assert the fallback message explicitly; `toHaveBeenCalled()` can pass from leftover call history instead of this click.</violation>
</file>

<file name="src/lib/utils.ts">

<violation number="1" location="src/lib/utils.ts:17">
P2: Strip trailing separators before taking the last segment; paths ending in `/` or `\\` currently return the full path instead of the folder name.</violation>

<violation number="2" location="src/lib/utils.ts:28">
P2: Match word tokens instead of splitting on whitespace here; Chinese punctuation is currently counted as an extra word.</violation>
</file>

<file name="src/lib/markdown.ts">

<violation number="1" location="src/lib/markdown.ts:10">
P2: Handle rendered markdown links explicitly; plain anchors in the preview will navigate the app window away from the editor.</violation>

<violation number="2" location="src/lib/markdown.ts:11">
P1: Resolve workspace image paths before returning preview HTML; relative markdown image URLs will not display in the Tauri preview.</violation>
</file>

<file name="src/components/file-browser.tsx">

<violation number="1" location="src/components/file-browser.tsx:45">
P1: Restrict new-file creation to the active workspace folder. Right now the save dialog can return a path outside `workspace.folderPath`, which leaves the editor on a file the browser cannot list or refresh correctly.</violation>
</file>

<file name="index.html">

<violation number="1" location="index.html:2">
P2: Hard-coding `class="light"` causes a wrong initial paint for saved dark-theme users until `applyTheme()` runs.</violation>
</file>

<file name="src/components/ui.tsx">

<violation number="1" location="src/components/ui.tsx:112">
P2: Forward native label attributes here; this wrapper currently drops `id`, `aria-*`, `data-*`, and other standard `<label>` props.</violation>
</file>

<file name="test/hooks/useI18n.test.tsx">

<violation number="1" location="test/hooks/useI18n.test.tsx:59">
P2: Assert the translated string here instead of any truthy value. Returning the key itself would still satisfy `toBeTruthy()` and hide i18n regressions.</violation>
</file>

<file name="src/state/editor.ts">

<violation number="1" location="src/state/editor.ts:33">
P1: `markAsSaved` should not overwrite the live `markdown` value or force `isModified` to `false`. If the user types while a save is in flight, this can lose those newer edits or mark them as already saved.</violation>
</file>

<file name="src/routes/dashboard.tsx">

<violation number="1" location="src/routes/dashboard.tsx:42">
P2: Use the autosave setting for this stat instead of `folderPath`; it currently reports autosave as ON whenever a folder is open.</violation>

<violation number="2" location="src/routes/dashboard.tsx:115">
P2: Restore the recent file's workspace before navigating home; setting only `selectedFilePath` can leave the editor hidden or the file browser out of sync.</violation>

<violation number="3" location="src/routes/dashboard.tsx:163">
P2: This `S3 Settings` shortcut only navigates home; it does not open any settings UI.</violation>
</file>

<file name="test/components/theme-core.test.tsx">

<violation number="1" location="test/components/theme-core.test.tsx:21">
P3: This test duplicates the existing `color-harmony.test.tsx` palette assertions and adds maintenance-only coverage.</violation>
</file>

<file name="test/components/comprehensive-theme.test.tsx">

<violation number="1" location="test/components/comprehensive-theme.test.tsx:13">
P2: This file no longer tests theme behavior, so the documented comprehensive theme suite loses its intended coverage.</violation>
</file>

<file name="src/routes/index.tsx">

<violation number="1" location="src/routes/index.tsx:35">
P2: This extra `loadFiles()` call duplicates the file scan that `FileBrowser` already triggers on `folderPath` changes.</violation>

<violation number="2" location="src/routes/index.tsx:121">
P2: `overflow-hidden` makes the welcome screen non-scrollable, so parts of the empty-state UI can be clipped behind the fixed header/footer.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

createEffect(() => {
const path = workspace.selectedFilePath

void (async () => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 9, 2026

Choose a reason for hiding this comment

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

P1: Race condition: rapid file-path changes can cause a stale readText result to overwrite the correct content. The async IIFE has no guard to discard results that belong to a superseded effect run.

Capture a version counter (or use an AbortController-style flag) and check it after the await to discard stale reads.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/writing-area.tsx, line 74:

<comment>Race condition: rapid file-path changes can cause a stale `readText` result to overwrite the correct content. The async IIFE has no guard to discard results that belong to a superseded effect run.

Capture a version counter (or use an `AbortController`-style flag) and check it after the `await` to discard stale reads.</comment>

<file context>
@@ -0,0 +1,492 @@
+  createEffect(() => {
+    const path = workspace.selectedFilePath
+
+    void (async () => {
+      if (!path) {
+        editorActions.reset()
</file context>
Fix with Cubic

solid(),
],
build: {
target: 'esnext',
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 9, 2026

Choose a reason for hiding this comment

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

P1: esnext is too new for Tauri's macOS/Linux webviews; use Tauri's platform-specific build targets instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At vite.config.ts, line 36:

<comment>`esnext` is too new for Tauri's macOS/Linux webviews; use Tauri's platform-specific build targets instead.</comment>

<file context>
@@ -0,0 +1,38 @@
+    solid(),
+  ],
+  build: {
+    target: 'esnext',
+  },
+})
</file context>
Fix with Cubic

recentFiles: [],
})

const [workspace, setWorkspace] = createStore<WorkspaceState>({
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 9, 2026

Choose a reason for hiding this comment

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

P1: Provide fallback values for properties parsed from local storage to prevent runtime crashes from missing keys.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/state/workspace.ts, line 36:

<comment>Provide fallback values for properties parsed from local storage to prevent runtime crashes from missing keys.</comment>

<file context>
@@ -0,0 +1,146 @@
+  recentFiles: [],
+})
+
+const [workspace, setWorkspace] = createStore<WorkspaceState>({
+  folderPath: persisted.folderPath,
+  selectedFilePath: persisted.selectedFilePath,
</file context>
Fix with Cubic

}

export async function saveS3Config(config: S3Config) {
return invokeCommand('save_s3_config', { config })
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 9, 2026

Choose a reason for hiding this comment

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

P1: Normalize blank optional S3 fields before saving. The dialog submits '' for endpoint_url and path_prefix, and Rust treats those as configured values, so leaving the endpoint blank can break every upload.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/s3.ts, line 17:

<comment>Normalize blank optional S3 fields before saving. The dialog submits `''` for `endpoint_url` and `path_prefix`, and Rust treats those as configured values, so leaving the endpoint blank can break every upload.</comment>

<file context>
@@ -0,0 +1,29 @@
+}
+
+export async function saveS3Config(config: S3Config) {
+  return invokeCommand('save_s3_config', { config })
+}
+
</file context>
Fix with Cubic

<CardDescription class="text-sm font-medium">{t('login.subtitle')}</CardDescription>
</CardHeader>
<CardContent>
<form class="grid gap-6" onSubmit={(event) => event.preventDefault()}>
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 9, 2026

Choose a reason for hiding this comment

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

P1: This onSubmit permanently disables the email/password login flow by preventing submission without invoking any auth logic.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/routes/login.tsx, line 37:

<comment>This `onSubmit` permanently disables the email/password login flow by preventing submission without invoking any auth logic.</comment>

<file context>
@@ -0,0 +1,96 @@
+            <CardDescription class="text-sm font-medium">{t('login.subtitle')}</CardDescription>
+          </CardHeader>
+          <CardContent>
+            <form class="grid gap-6" onSubmit={(event) => event.preventDefault()}>
+              <div class="flex flex-col gap-3">
+                <Button variant="outline" class="w-full">
</file context>
Fix with Cubic

}))

describe('solid route integration', () => {
it('renders dashboard stats from workspace state', () => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 9, 2026

Choose a reason for hiding this comment

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

P2: This file no longer tests theme behavior, so the documented comprehensive theme suite loses its intended coverage.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/components/comprehensive-theme.test.tsx, line 13:

<comment>This file no longer tests theme behavior, so the documented comprehensive theme suite loses its intended coverage.</comment>

<file context>
@@ -1,222 +1,46 @@
+}))
+
+describe('solid route integration', () => {
+  it('renders dashboard stats from workspace state', () => {
+    workspaceActions.setFolderPath('/workspace')
+    workspaceActions.setFiles([
</file context>
Fix with Cubic


const [isCheckingUpdate, setIsCheckingUpdate] = useState(false);
workspaceActions.setFolderPath(result)
await workspaceActions.loadFiles()
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 9, 2026

Choose a reason for hiding this comment

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

P2: This extra loadFiles() call duplicates the file scan that FileBrowser already triggers on folderPath changes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/routes/index.tsx, line 35:

<comment>This extra `loadFiles()` call duplicates the file scan that `FileBrowser` already triggers on `folderPath` changes.</comment>

<file context>
@@ -0,0 +1,257 @@
+      if (!result) return
+
+      workspaceActions.setFolderPath(result)
+      await workspaceActions.loadFiles()
+    } catch (error) {
+      toast.error(getErrorMessage(error))
</file context>
Fix with Cubic

<div className="h-[8px] flex-1 bg-foreground"></div>
<div className="border-2 border-foreground p-1">
<div className="w-4 h-4 bg-foreground"></div>
<div class="flex flex-1 overflow-hidden pb-20 pt-24">
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 9, 2026

Choose a reason for hiding this comment

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

P2: overflow-hidden makes the welcome screen non-scrollable, so parts of the empty-state UI can be clipped behind the fixed header/footer.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/routes/index.tsx, line 121:

<comment>`overflow-hidden` makes the welcome screen non-scrollable, so parts of the empty-state UI can be clipped behind the fixed header/footer.</comment>

<file context>
@@ -0,0 +1,257 @@
+        </Card>
+      </div>
+
+      <div class="flex flex-1 overflow-hidden pb-20 pt-24">
+        <Show
+          when={workspace.folderPath}
</file context>
Fix with Cubic

},
]
it('switches the app language from the landing page selector', async () => {
render(() => <HomePage />)
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 9, 2026

Choose a reason for hiding this comment

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

P3: Clear the global window.alert mock and assert the fallback message explicitly; toHaveBeenCalled() can pass from leftover call history instead of this click.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/components/interaction-states.test.tsx, line 37:

<comment>Clear the global `window.alert` mock and assert the fallback message explicitly; `toHaveBeenCalled()` can pass from leftover call history instead of this click.</comment>

<file context>
@@ -1,182 +1,56 @@
-        },
-      ]
+  it('switches the app language from the landing page selector', async () => {
+    render(() => <HomePage />)
 
-      render(<NavDocuments items={mockDocuments} />, { withSidebar: true })
</file context>
Fix with Cubic

expect(container.innerHTML).toContain('Bordered content')
expect(container.innerHTML).toContain('<div')
})
it('defines light and dark theme tokens in the stylesheet', () => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 9, 2026

Choose a reason for hiding this comment

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

P3: This test duplicates the existing color-harmony.test.tsx palette assertions and adds maintenance-only coverage.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/components/theme-core.test.tsx, line 21:

<comment>This test duplicates the existing `color-harmony.test.tsx` palette assertions and adds maintenance-only coverage.</comment>

<file context>
@@ -1,204 +1,39 @@
-      expect(container.innerHTML).toContain('Bordered content')
-      expect(container.innerHTML).toContain('<div')
-    })
+  it('defines light and dark theme tokens in the stylesheet', () => {
+    expect(globalsCss).toContain('--background: #ffffff;')
+    expect(globalsCss).toContain('--foreground: #000000;')
</file context>
Fix with Cubic

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