Skip to content

Commit 590e368

Browse files
committed
fix(desktop): mirror runtime import graph
1 parent d96c82e commit 590e368

6 files changed

Lines changed: 61 additions & 25 deletions

File tree

apps/desktop/AGENTS.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,25 @@ typecheck.
138138
real `exports` field. So bundle behaviour is unchanged; only `vue-tsc`
139139
follows the stubs.
140140

141+
The typecheck import graph must still mirror the runtime import graph. When
142+
desktop reuses workspace package exports, source files must import through
143+
the same public module specifiers that Vite bundles at runtime, and the
144+
renderer stubs must model that same surface. Do not paper over `vue-tsc`
145+
errors by swapping imports to aliases or private source paths unless both
146+
Vite and `tsconfig.web.json` intentionally resolve that specifier to the
147+
same module. A resolver mismatch can make one pipeline pass while the other
148+
fails, or make typecheck validate a different module than the one packaged
149+
at runtime.
150+
141151
When you add a new `@memohai/web/*` import in the desktop renderer, add a
142152
matching `declare module` to `web-stubs.d.ts`. The wildcard
143153
`declare module '@memohai/web/*.vue'` already covers any `.vue` SFC reached
144154
through the wildcard `./*` export.
145155

156+
When you import a new component directly from `@memohai/ui`, add that
157+
component to `ui-stubs.d.ts` as well. The package may export it correctly,
158+
but desktop's renderer typecheck only sees the stubbed surface.
159+
146160
## Multi-Window Lifecycle
147161

148162
### Main process (`src/main/index.ts`)
@@ -552,6 +566,14 @@ list to check.
552566
- **Update both `tsconfig.web.json` paths and `web-stubs.d.ts`** when adding
553567
a new `@memohai/web/foo` import. Forgetting the stub yields
554568
`TS2307: Cannot find module` even though the bundle works.
569+
- **Keep typecheck resolution and runtime resolution isomorphic.** Desktop
570+
renderer code must import reused workspace modules through the same public
571+
specifiers that Vite bundles at runtime, then model that surface in the
572+
stubs. Do not mix in alternate aliases or private source paths unless both
573+
Vite and `tsconfig.web.json` intentionally resolve them to the same target.
574+
- **Update `ui-stubs.d.ts` for direct `@memohai/ui` imports.** A component
575+
exported by the real UI package still needs to exist in the desktop stub
576+
if desktop imports it directly.
555577
- **Run `pnpm --filter @memohai/desktop typecheck` after every renderer
556578
change.** It's fast (only types desktop's own code thanks to the stubs)
557579
and catches the common drift cases (missing stub, wrong store/component

apps/desktop/src/renderer/src/settings/App.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ const breadcrumbs = computed(() => {
5656
}
5757
}
5858
if (items.length > 0) {
59-
items[items.length - 1].isLast = true
59+
const lastItem = items[items.length - 1]
60+
if (lastItem) lastItem.isLast = true
6061
}
6162
return items
6263
})

apps/desktop/src/renderer/src/settings/router.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { createRouter, createMemoryHistory, type RouteRecordRaw } from 'vue-router'
1+
import { h } from 'vue'
2+
import { createRouter, createMemoryHistory, RouterView, type RouteRecordRaw } from 'vue-router'
23
import { SETTINGS_ROUTE_SPECS, SETTINGS_DEFAULT_PATH, type SettingsRouteSpec } from '../shared/settings-routes'
34

45
// Settings-window router. Mirrors the path layout under `/settings/*` from
@@ -8,13 +9,17 @@ import { SETTINGS_ROUTE_SPECS, SETTINGS_DEFAULT_PATH, type SettingsRouteSpec } f
89
// into `/settings/bots` (or whatever path the chat window asks for via the
910
// `settings:navigate` IPC).
1011

11-
const mapSpecToRoute = (spec: SettingsRouteSpec): RouteRecordRaw => ({
12-
name: spec.name,
13-
path: spec.path,
14-
component: spec.loader,
15-
meta: spec.meta,
16-
children: spec.children?.map(mapSpecToRoute),
17-
})
12+
const mapSpecToRoute = (spec: SettingsRouteSpec): RouteRecordRaw => {
13+
const route = {
14+
path: spec.path,
15+
component: spec.loader ?? { render: () => h(RouterView) },
16+
...(spec.name ? { name: spec.name } : {}),
17+
...(spec.meta ? { meta: spec.meta } : {}),
18+
...(spec.children ? { children: spec.children.map(mapSpecToRoute) } : {}),
19+
} satisfies RouteRecordRaw
20+
21+
return route
22+
}
1823

1924
const realRoutes: RouteRecordRaw[] = SETTINGS_ROUTE_SPECS.map(mapSpecToRoute)
2025

apps/desktop/src/renderer/src/shared/settings-routes.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// children — adding a new settings page to web means adding an entry here.
88

99
import type { Component } from 'vue'
10-
import { i18nRef } from '@/i18n'
10+
import { i18nRef } from '@memohai/web/i18n'
1111

1212
export interface SettingsRouteSpec {
1313
name?: string
@@ -27,92 +27,92 @@ export const SETTINGS_ROUTE_SPECS: SettingsRouteSpec[] = [
2727
{
2828
name: 'bots',
2929
path: '',
30-
loader: () => import('@/pages/bots/index.vue'),
30+
loader: () => import('@memohai/web/pages/bots/index.vue'),
3131
},
3232
{
3333
name: 'bot-new',
3434
path: 'new',
35-
loader: () => import('@/pages/bots/new.vue'),
35+
loader: () => import('@memohai/web/pages/bots/new.vue'),
3636
meta: { breadcrumb: i18nRef('bots.createBot') }
3737
},
3838
{
3939
name: 'bot-detail',
4040
path: ':botId',
41-
loader: () => import('@/pages/bots/detail.vue'),
41+
loader: () => import('@memohai/web/pages/bots/detail.vue'),
4242
meta: { breadcrumb: (route: { params: { botId?: string } }) => route.params.botId }
4343
},
4444
]
4545
},
4646
{
4747
name: 'providers',
4848
path: '/settings/providers',
49-
loader: () => import('@/pages/providers/index.vue'),
49+
loader: () => import('@memohai/web/pages/providers/index.vue'),
5050
meta: { breadcrumb: i18nRef('sidebar.providers') }
5151
},
5252
{
5353
name: 'web-search',
5454
path: '/settings/web-search',
55-
loader: () => import('@/pages/web-search/index.vue'),
55+
loader: () => import('@memohai/web/pages/web-search/index.vue'),
5656
meta: { breadcrumb: i18nRef('sidebar.webSearch') }
5757
},
5858
{
5959
name: 'memory',
6060
path: '/settings/memory',
61-
loader: () => import('@/pages/memory/index.vue'),
61+
loader: () => import('@memohai/web/pages/memory/index.vue'),
6262
meta: { breadcrumb: i18nRef('sidebar.memory') }
6363
},
6464
{
6565
name: 'speech',
6666
path: '/settings/speech',
67-
loader: () => import('@/pages/speech/index.vue'),
67+
loader: () => import('@memohai/web/pages/speech/index.vue'),
6868
meta: { breadcrumb: i18nRef('sidebar.speech') }
6969
},
7070
{
7171
name: 'transcription',
7272
path: '/settings/transcription',
73-
loader: () => import('@/pages/transcription/index.vue'),
73+
loader: () => import('@memohai/web/pages/transcription/index.vue'),
7474
meta: { breadcrumb: i18nRef('sidebar.transcription') }
7575
},
7676
{
7777
name: 'email',
7878
path: '/settings/email',
79-
loader: () => import('@/pages/email/index.vue'),
79+
loader: () => import('@memohai/web/pages/email/index.vue'),
8080
meta: { breadcrumb: i18nRef('sidebar.email') }
8181
},
8282
{
8383
name: 'usage',
8484
path: '/settings/usage',
85-
loader: () => import('@/pages/usage/index.vue'),
85+
loader: () => import('@memohai/web/pages/usage/index.vue'),
8686
meta: { breadcrumb: i18nRef('sidebar.usage') }
8787
},
8888
{
8989
name: 'appearance',
9090
path: '/settings/appearance',
91-
loader: () => import('@/pages/appearance/index.vue'),
91+
loader: () => import('@memohai/web/pages/appearance/index.vue'),
9292
meta: { breadcrumb: i18nRef('sidebar.appearance') }
9393
},
9494
{
9595
name: 'profile',
9696
path: '/settings/profile',
97-
loader: () => import('@/pages/profile/index.vue'),
97+
loader: () => import('@memohai/web/pages/profile/index.vue'),
9898
meta: { breadcrumb: i18nRef('sidebar.profile') }
9999
},
100100
{
101101
name: 'platform',
102102
path: '/settings/platform',
103-
loader: () => import('@/pages/platform/index.vue'),
103+
loader: () => import('@memohai/web/pages/platform/index.vue'),
104104
meta: { breadcrumb: i18nRef('sidebar.platform') }
105105
},
106106
{
107107
name: 'supermarket',
108108
path: '/settings/supermarket',
109-
loader: () => import('@/pages/supermarket/index.vue'),
109+
loader: () => import('@memohai/web/pages/supermarket/index.vue'),
110110
meta: { breadcrumb: i18nRef('sidebar.supermarket') }
111111
},
112112
{
113113
name: 'about',
114114
path: '/settings/about',
115-
loader: () => import('@/pages/about/index.vue'),
115+
loader: () => import('@memohai/web/pages/about/index.vue'),
116116
meta: { breadcrumb: i18nRef('sidebar.about') }
117117
},
118118
]

apps/desktop/src/renderer/types/ui-stubs.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ declare module '@memohai/ui' {
2222
export const AvatarFallback: LooseComponent
2323
export const AvatarImage: LooseComponent
2424
export const Badge: LooseComponent
25+
export const Breadcrumb: LooseComponent
26+
export const BreadcrumbItem: LooseComponent
27+
export const BreadcrumbLink: LooseComponent
28+
export const BreadcrumbList: LooseComponent
29+
export const BreadcrumbPage: LooseComponent
30+
export const BreadcrumbSeparator: LooseComponent
2531
export const Button: LooseComponent
2632
export const Card: LooseComponent
2733
export const CardHeader: LooseComponent

apps/desktop/src/renderer/types/web-stubs.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ declare module '@memohai/web/router' {
1717

1818
declare module '@memohai/web/i18n' {
1919
import type { I18n } from 'vue-i18n'
20+
import type { ComputedRef } from 'vue'
2021
const i18n: I18n
2122
export default i18n
23+
export function i18nRef(key: string): ComputedRef<string>
2224
}
2325

2426
declare module '@memohai/web/api-client' {

0 commit comments

Comments
 (0)