feat(desktop): desktop shell foundation and project hub (#172)#193
Open
Dencyuman wants to merge 12 commits into
Open
feat(desktop): desktop shell foundation and project hub (#172)#193Dencyuman wants to merge 12 commits into
Dencyuman wants to merge 12 commits into
Conversation
Add a stdlib generator that emits src/tokens.css from brand/tokens.json: primitives and light-theme semantics at :root, dark and high-contrast overriding by data-theme attribute. Semantic values resolve to primitive CSS vars so components reference only semantic tokens. Wire it into the package generate script (covered by the make generate reproducibility gate), ship it as a ./tokens.css export, and prepend it to the desktop frontend stylesheet bundle. Add a drift test asserting the committed file matches the generator output.
Introduce a dependency-free translator with an I18nProvider/useI18n context, named-argument interpolation, Intl date/number/relative-time formatting, and a reason-plus-code error() helper for closed diagnostic codes. Ship a shared shell catalog with a canonical en base and a complete ja translation (hub, workspace frame, lifecycle, and diagnostic reasons); locales are plain data so adding one never touches a component. Export it as ./i18n with a completeness test asserting ja covers every base key.
…d i18n Redesign the project hub as a left rail (wordmark + Projects/Library nav) with a Projects header, primary/secondary actions, a reason-plus-code error banner with a Show details toggle, and dense recent rows (name + relative time, no internal identifiers or OS paths). Wrap the shell in an I18nProvider that follows the OS locale and route every hub, workspace, lifecycle, and error string through the shared catalog. Give the workspace a topbar with a breadcrumb back to Projects and a Structure/Views mode switch, keeping the three-pane landmarks intact. Rewrite the stylesheet to resolve entirely from the generated token variables (flat elevation, token radii, 13px workbench density) so light/dark/high-contrast follow the token set with no per-theme overrides. Add a non-throwing useOptionalI18n so the shell renders with a default translator in tests. Update the shell, boundary, and e2e tests to the new structure and token-based styling.
Record the Phase 2 path for resolving Wails native-menu labels from the shared message catalog: the frontend resolves labels through the active translator and passes already-localized strings across the bridge (Go never owns translation), matching the diagnostic-code contract and the binding-parity fixtures that gate adding the binding.
Introduce a Tailwind v4 CLI build step (no bundler required) whose @theme maps exclusively onto the generated token custom properties — the default Tailwind palette is never imported, content scanning is restricted to the primitives module, and the emitted stylesheet contains no raw palette values. Copy in a minimal shadcn/ui-style primitive set over Base UI (Button, Input, Tabs, Dialog, DropdownMenu, Select) fully de-shadcn'd: token radii capped at 4/6/8px, flat elevation via shadow tokens only, 13px workbench density, semantic token colors throughout. Ship as ./primitives with ./primitives.css and tests asserting the token-only theme and closed-overlay behavior.
…icon Generate src/brand-assets.ts from brand/png (committed output, regenerated via the package generate script) so Desktop chrome renders the canonical LayerDraw icon instead of a hand-drawn substitute, and rebuild the wordmark row as icon-plus-ink-text per the brand kit. Replace the Wails default build/appicon.png with brand/png/layerdraw-icon-1024.png so the packaged app's Dock and Finder icon carry the LayerDraw brand, and ignore the generated build/bin and build/darwin outputs.
Rebuild the hub to the approved layout: 224px rail on the app background with brand row, icon nav, and a live MCP status chip; inline page header actions on the shared Button primitive; a danger banner with icon, bold reason, mono stable code, Show details, and dismiss; Recent as one bordered container with subtle dividers and dense rows (brand file icon, 500-weight name, mono location line when the host provides one, tabular relative time, chevron, canvas hover). Legacy recents whose stored name is a raw internal identifier render the localized untitled label — internal IDs never reach primary UI. Template cards render only when a connected Library source exists, from real Registry template results plus a dashed blank-project card, replacing the unstyled hub Library panel. The workspace mode switch now uses the shared Tabs primitive, and the frontend stylesheet bundle includes the emitted primitives layer.
The owner removed the rail-footer MCP chip from the approved mock as contextless; MCP status surfaces through the View menu and the Settings screen instead. Remove the chip, its styles, its status lookup, and the now-unused catalog entries; the rail footer stays empty.
Remove the UA body margin so the shell owns the window, fix the primary button foreground losing its specificity battle against the shell button reset, replace the icon-plus-text brand row with the canonical logo lockups (light/dark), compose the app icon into the macOS 1024/824 grid so the system stops shrinking it, strip internal identifiers and state-machine readouts from the workspace (abnormal statuses surface as localized chips), fold the Library/Review/AI panels into localized inspector sections without raw session identifiers, localize the remaining workspace strings (en/ja), and record the committed project name into recents instead of the pre-commit bootstrap title.
Adds the full native menu (LayerDraw with live Language switching, File with Open Recent and Close Project, Edit, View commands over a frontend event bridge, Window, Help), threads a display-only location_label through recents on both the dialog and reload open paths, masks internal document IDs in the Open Recent menu, and documents the label as hub-display-only in the desktop contracts.
Centers the hub content column in the main pane, enlarges the brand lockup, keeps exactly one Language checkmark by rebuilding the menu from tracked state on selection, and adds a LAYERDRAW_STATE_ROOT override so QA runs stop writing into the production recents store.
There was a problem hiding this comment.
Pull request overview
Builds the Phase 1 Desktop shell foundation: introduces token-driven styling + shared UI primitives, adds a message-catalog i18n layer with runtime locale switching, and expands the Wails desktop menu / project lifecycle plumbing to support hub ⇄ workspace navigation without restarting.
Changes:
- Add
@layerdraw/reacti18n + Base UI–backed primitives (Tailwind v4 theme mapped to generated token CSS) with tests to prevent shadcn defaults and stale generated outputs. - Implement the Desktop hub + three-pane workspace shell styling and wiring (recents display rules, templates section, inspector sections, locale switching).
- Extend the Go/Wails backend + contracts for recents display metadata (
location_label) and project close/navigation, plus native menu scaffolding.
Reviewed changes
Copilot reviewed 53 out of 63 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-workspace.yaml | Workspace-level pnpm build policy adjustment. |
| packages/react/test/tokens-css.test.mjs | Tests ensuring generated tokens.css stays in sync and enforces token invariants. |
| packages/react/test/primitives.test.mjs | Tests for primitives exports + emitted CSS constraints (no shadcn palette bleed). |
| packages/react/test/package-boundary.test.mjs | Updates export-surface expectations for new i18n/primitives/tokens entries. |
| packages/react/test/i18n.test.mjs | Unit tests for catalog completeness, locale resolution, interpolation, formatting, and provider behavior. |
| packages/react/src/tokens.css | Generated token custom properties for light/dark/high-contrast themes. |
| packages/react/src/primitives/tabs.ts | Base UI Tabs wrapper restyled to tokenized segmented control. |
| packages/react/src/primitives/select.ts | Base UI Select wrapper restyled with token-driven classes. |
| packages/react/src/primitives/menu.ts | Base UI Menu wrapper restyled with token-driven classes. |
| packages/react/src/primitives/input.ts | Base UI Input wrapper restyled with token-driven classes. |
| packages/react/src/primitives/index.ts | Barrel export for internal primitives module. |
| packages/react/src/primitives/dialog.ts | Base UI Dialog wrapper restyled with token-driven classes. |
| packages/react/src/primitives/cn.ts | Minimal cn() helper to avoid runtime deps. |
| packages/react/src/primitives/button.ts | Base UI Button wrapper with tokenized variants/sizes. |
| packages/react/src/primitives.css | Tailwind v4 theme mapping to generated token variables + utilities import. |
| packages/react/src/index.ts | Exposes i18n via the package root entrypoint. |
| packages/react/src/i18n.ts | New dependency-free i18n catalog system + shared shell catalogs (en/ja). |
| packages/react/scripts/generate-tokens-css.mjs | Generator producing src/tokens.css from brand/tokens.json. |
| packages/react/package.json | Adds Base UI + Tailwind CLI build outputs and exports tokens/primitives CSS artifacts. |
| internal/desktopwails/run.go | Native menu expansion (Language switching, recents, close project, view/panel events). |
| internal/desktopwails/run_test.go | Adjusts native menu test for new structure and labels. |
| internal/desktopwails/frontend.go | Adds CloseCurrentProject bridge method for hub navigation. |
| internal/desktopapp/projects.go | Threads committed display name into recents + computes location_label. |
| internal/desktopapp/project_lifecycle.go | Persists/returns location_label in recent project records. |
| internal/desktopapp/project_lifecycle_test.go | Updates lifecycle tests for new opened(..., locationLabel) signature. |
| internal/desktopapp/project_lifecycle_api.go | Exposes ActiveSessions() for shell close behavior. |
| go.sum | Dependency graph reshaping from updated module usage. |
| docs/desktop-wails-contracts.md | Documents location_label as display-only hub metadata. |
| docs/desktop-native-menu-i18n.md | Design memo for future native-menu localization bridge. |
| apps/desktop/test/shell.test.mjs | Updates shell tests for hub title/actions ordering and recents ID-masking + location/time display. |
| apps/desktop/test/package-boundary.test.mjs | Updates desktop styles test to assert token-variable layout widths. |
| apps/desktop/test/library-panel.test.mjs | Updates tests for localized/renamed library form labels. |
| apps/desktop/test/browser-e2e.mjs | Loads tokens/primitives CSS in E2E and updates selectors/labels for new UI. |
| apps/desktop/src/wails-bootstrap.ts | Wails composition updates including CloseCurrentProject integration. |
| apps/desktop/src/viewer-surface.ts | Viewer empty/attention strings now use i18n (partial). |
| apps/desktop/src/styles.css | Large rewrite to token-driven desktop shell styling + bundled fonts. |
| apps/desktop/src/shell.ts | New hub + workspace shell structure, i18n integration, recents/templates rendering, inspector sections. |
| apps/desktop/src/mount.ts | Wraps shell in I18nProvider and adds runtime locale override API. |
| apps/desktop/src/mcp-panel.ts | MCP panel strings moved to shared i18n and updates formatting/scopes labels. |
| apps/desktop/src/library-panel.ts | Library panel strings moved to shared i18n. |
| apps/desktop/src/editor-surface.ts | Editor toolbar strings moved to shared i18n; removes non-user-actionable state summary. |
| apps/desktop/src/contracts.ts | Adds projectDialogs.close() to support return-to-hub navigation. |
| apps/desktop/src/brand.ts | Adds brand icon/wordmark components using generated embedded assets. |
| apps/desktop/scripts/normalize-wails-bindings.mjs | Ensures SPDX headers and normalizes generated Wails bindings; now includes ShellBinding. |
| apps/desktop/scripts/generate-brand-assets.mjs | Generates brand-assets.ts from canonical brand/ assets. |
| apps/desktop/scripts/build-frontend.mjs | Bundles fonts and concatenates tokens/primitives/editor/desktop CSS into app bundle. |
| apps/desktop/package.json | Adds generate script for brand assets and keeps build pipeline consistent. |
| apps/desktop/main.go | Adds LAYERDRAW_STATE_ROOT override with absolute-clean-path validation. |
| apps/desktop/frontend/wailsjs/runtime/runtime.js | Regenerated Wails runtime JS artifact (header consistency issue noted in comments). |
| apps/desktop/frontend/wailsjs/runtime/runtime.d.ts | Regenerated Wails runtime d.ts artifact (header consistency issue noted in comments). |
| apps/desktop/frontend/wailsjs/go/models.ts | Updates generated Go model bindings for new types/fields (location_label, close result). |
| apps/desktop/frontend/wailsjs/go/desktopwails/ShellBinding.js | New generated ShellBinding JS bridge artifact. |
| apps/desktop/frontend/wailsjs/go/desktopwails/ShellBinding.d.ts | New generated ShellBinding d.ts bridge artifact. |
| apps/desktop/frontend/wailsjs/go/desktopwails/FrontendBridge.js | Adds CloseCurrentProject binding export. |
| apps/desktop/frontend/wailsjs/go/desktopwails/FrontendBridge.d.ts | Adds CloseCurrentProject binding type. |
| apps/desktop/frontend/src/main.ts | Wires native menu events to locale switching + menu command bridge; return-to-hub via close binding. |
| apps/desktop/.gitignore | Ignores desktop build output directories. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, | ||
| }; | ||
|
|
||
| /** Assert that every non-base locale defines exactly the base locale's keys. */ |
| OpenProjectDialog(requestID: string): Promise<DesktopHostResult<DesktopProjectOpenDTO>>; | ||
| RecentProjects(): Promise<DesktopHostResult<readonly DesktopRecentProjectDTO[]>>; | ||
| OpenRecentProject(projectID: string): Promise<DesktopHostResult<DesktopProjectOpenDTO>>; | ||
| CloseCurrentProject(): Promise<{ readonly outcome: string }>; |
Comment on lines
+222
to
+226
| close: async () => { | ||
| const result = await application.CloseCurrentProject(); | ||
| await lifecycle.refreshPublication(); | ||
| return result as never; | ||
| }, |
| }, [hubVisible, projectDialogs, dialogPending]); | ||
|
|
||
| const failure = state.failure === undefined ? null : suppliedLabels.failure[state.failure.message_key]; | ||
| const failure = state.failure === undefined ? null : t.t(`error.${state.failure.message_key}`); |
| library === undefined ? null : inspectorSection(t.t("inspector.section.library"), false, createElement(DesktopLibraryPanel, { library, project: project.library_project })), | ||
| reviewModel === undefined ? null : inspectorSection(t.t("inspector.section.review"), false, createElement(ReviewPanel, { model: reviewModel })), | ||
| inspectorSection(t.t("inspector.section.mcp"), false, createElement(DesktopMCPPanel, { mcp: mcp ?? unavailableMCP, projectID: project.project_id })))), | ||
| createElement("div", { className: "ld-desktop-visually-hidden", role: "status", "aria-live": "polite", "aria-atomic": true }, state.pending_action === "select_view" ? "Opening view…" : state.pending_action === "review_recovery" ? "Opening recovery options…" : state.pending_action === "disconnect_storage" ? "Disconnecting external storage…" : "")); |
Comment on lines
80
to
86
| if (state.status === "loading" || state.status === "cancelling") return createElement("p", { role: "status", "aria-live": "polite" }, "Loading view…"); | ||
| if (state.status === "empty") return createElement("p", { className: "ld-desktop-empty" }, state.reason === "view_empty" ? "This view is empty." : "Select a view to begin."); | ||
| if (state.status === "empty") return createElement("p", { className: "ld-desktop-empty" }, s_translator.t(state.reason === "view_empty" ? "workspace.empty.view" : "workspace.empty.select")); | ||
| if (state.status === "disposed") return createElement("p", { role: "status" }, "Viewer closed."); | ||
| const publication = "publication" in state ? state.publication : state.previous; | ||
| return createElement("div", { className: "ld-desktop-viewer", "data-viewer-state": state.status }, | ||
| publication?.render_data.kind !== "diagram" ? null : createElement("div", { className: "ld-desktop-view-mode", role: "toolbar", "aria-label": "Viewer dimension" }, | ||
| createElement("button", { type: "button", "data-mode": "2d", "aria-pressed": mode === "2d", onClick: () => setMode("2d") }, "2D"), |
Comment on lines
+303
to
+309
| appMenu := result.AddSubmenu("LayerDraw") | ||
| appMenu.AddText("About LayerDraw", nil, func(*menu.CallbackData) { | ||
| _, _ = wailsruntime.MessageDialog(bridge.context(), wailsruntime.MessageDialogOptions{ | ||
| Type: wailsruntime.InfoDialog, | ||
| Title: "LayerDraw", | ||
| Message: "LayerDraw Desktop\nTyped-graph modeling for software architecture.", | ||
| }) |
| @@ -1,5 +1,3 @@ | |||
| // SPDX-License-Identifier: LicenseRef-LayerDraw-1.0 | |||
|
|
|||
| /* | |||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #172 (Phase 1: shell foundation).
What
brand/tokens.json→ CSS variables only).@layerdraw/react; locale follows the OS with an in-session override from the menu.location_labelthreading (dialog and reload open paths),CloseCurrentProjectbridge,LAYERDRAW_STATE_ROOToverride for isolated QA state.docs/desktop-wails-contracts.mddocumentslocation_labelas hub-display-only.Verification
go build ./..., desktopapp/desktopwails/apps suites green.