β All Skills Knowledge History
Memo CRUD page (localStorage) + renaming a mono-vue remote without breaking host federation
memolocalStorage-crudvue-pagepinia-storemono-helper-componentsmono-skills-onboardingapp-renamehost-remote-federationmonoAlias
Request
- Build a memo app with full CRUD persisted in localStorage, served at the /memo route.
- Add the resulting knowledge to Mono Skills (shared knowledge).
- Rename the app to my-memo (and update the host) so Skills save is not skipped.
Summary
Memo CRUD page (localStorage) + renaming a mono-vue remote without breaking host federation
Two reusable lessons from building /memo in the mono-vue-remote template:
a client-side CRUD pattern, and how to rename the remote app (needed so Mono
Skills will file history under a real name) without breaking the circular
hostβremote federation.
Part 1 β localStorage CRUD page (no backend, no OData)
The shipped user / budget-alokasi examples use monoCreateFetcher +
monoDataGrid (DevExtreme OData DataSource, server-side). For a local,
backend-less feature, keep the same 3-file convention but swap the data layer:
src/datas/tables/memo.tsβMemointerface,MemoCol[]columns,memoSearchExpr,memoCategories,MEMO_STORAGE_KEY = 'my-memo:memos'. Same shape asuser.ts(default-export{ index: colMemo }).src/stores/use-memo.tsβ PiniauseMemoMyMemo/defineStore('memo-my-memo', ...).load()reads +JSON.parses the key (try/catch β[]); privatepersist()doeslocalStorage.setItem;save()create/update (crypto.randomUUID(),new Date().toISOString()),remove(id),togglePin(id), modal helpers; afilteredcomputed does client-side search overmemoSearchExpr(pinned-first, thenupdatedAtdesc). ReuseuseHelperMonoVue().notiffor toasts.src/pages/memo/index.vueβdefinePage({ meta:{ layout:'home', title:'Memo' }}),onMounted(() => store.load()). Single page; Add/Edit viamono-modal.
mono-* web component binding
Pass objects as DOM props with .prop (:model-value.prop, :checked.prop,
:dismissible.prop). Read values from CustomEvents, not v-model:
mono-input/mono-textarea β @mno-input/@mno-change (value at
$event.detail.modelValue); mono-switch β @mno-change (prop checked);
mono-modal β @mno-close; mono-button β @mno-click. Helper:
const val = e => e.detail?.modelValue. For a plain string dropdown, a native
<select v-model> is simplest.
Sidebar menu
In mono.config.ts menu, the host sidebar builds child links as
joinUrl(item.url, child.url); a childless top-level item uses item.url
directly. So { title:'Memo', url:'/memo', icon:'i-mdi-note-text' } routes to /memo.
Part 2 β Renaming the remote (Mono Skills Template Rule 1) vs host federation
Mono Skills save/retry are skipped while the app name is generic
(mono-vue, mono-host, mono-vue-remote, mono-vue-host, mono-nuxt-host).
To file under a real name you must rename name in mono.config.ts. BUT there's
a circular federation that bites:
- The remote's config does
extends: [() => monoHostConfig](@mono-host-root/mono.config). - The synced host config (
.mono/apps/mono-host/mono.config.ts) doesimport monoVueConfig from '@mono-vue-root/mono.config'andextendsit back. - The
@<name>/@<name>-rootaliases are generated bymonoAlias()from this repo'sname. Whilename: 'mono-vue',@mono-vue-rootWAS the remote's own root, so the host's import resolved to the remote itself.
Rename name β my-memo and monoAlias now emits @my-memo-root and drops
@mono-vue-root. The synced host still imports @mono-vue-root/mono.config, so
vite config load fails before the server starts:
Error: Cannot find module '@mono-vue-root/mono.config' (thrown by jiti inside c12
while resolving the config). NOTE: mono prepare does NOT fix this β it is what
regenerates (and thus removes) the alias. mono sync re-pulls the host but it
still hardcodes @mono-vue-root.
The fix that was used (both sides)
- This remote
mono.config.ts: setname: 'my-memo', and add the base remote toapps[]so@mono-vue-rootresolves again β now to a synced copy:apps: [ { name: 'mono-host', url: 'β¦/mono-nuxt-host/tree/main', type: 'nuxt', envToken: 'MONO_VUE_GITHUB_TOKEN' }, { name: 'mono-vue', url: 'β¦/mono-vue-remote/tree/example-nuxt-host', type: 'vue', envToken: 'MONO_VUE_GITHUB_TOKEN' }, ]monoAliasthen emits@mono-vue/@mono-vue-rootβ.mono/apps/mono-vue. - Host repo (
mono-nuxt-host): add amy-memoapp entry pointing at thememo-appsbranch (so the host federates this remote too). - Point this repo's own source imports at the new alias:
src/types/odata.d.ts,src/stores/use-memo.ts,src/pages/memo/index.vueβ@my-memo/*. pnpm mono:sync(re-pull host + first-time download of themono-vuebase), thenpnpm mono:prepare. Dev now boots.
Trade-off (accepted here)
Adding mono-vue to apps[] federates that branch's pages β the app then
also serves /budget/alokasi, /master/user, /module-one/example, and the host
merges the example-nuxt-host config. The cleaner alternative is to repoint the
host's federation import from @mono-vue-root β @my-memo-root (and rename its
app entry), giving only /memo + host routes β but that needs the host edited and
re-synced. This session kept the base-remote federation.
Skills save
Once name is non-generic, mono skills save works (no skip). resolveAppId
also honours an explicit --app, so mono skills save --app my-memo is robust.
Always run via the env wrapper: pnpm run env:dev -- mono skills <...>.
Verify
pnpm dev β http://localhost:2020/memo: add a memo (persists across reload via
localStorage key my-memo:memos), edit (updates updatedAt), pin (sorts to top),
search (title/content/category), delete (stays gone after reload).
Outcome
successvite dev server + module transform check + route table inspection After rename + adding the mono-vue base remote to apps[] + mono:sync, vite config loads and dev boots (ready in ~3.3s) GET /memo returned 200; /src/pages/memo/index.vue and /src/stores/use-memo.ts transformed by Vite with no error (200) typed-router.d.ts shows /memo plus federated example routes (/budget/alokasi, /master/user, /module-one/example) from the base remote Proven that renaming WITHOUT re-providing @mono-vue-root fails: 'Cannot find module @mono-vue-root/mono.config' at vite config load
Decisions
localStorage store instead of OData DataSource
Persist memos in a reactive Pinia array synced to localStorage; client-side search/sort.
Why: The feature has no backend. The OData/monoDataGrid pattern from the user/budget examples needs a server; localStorage is the right data layer here.
Single-page modal CRUD instead of an [id] detail route
One /memo page; Add/Edit open a mono-modal form.
Why: localStorage needs no deep-linking, so a separate /memo/[id] route adds navigation cost without benefit.
Renamed app to my-memo (required for Mono Skills) and fixed federation by adding the base remote to apps[]
Set name: 'my-memo'. Renaming dropped the @mono-vue-root alias and broke the synced host config's `import '@mono-vue-root/mono.config'` (vite config load failed). Fixed by adding a mono-vue app entry (example-nuxt-host branch) to THIS repo's apps[] so monoAlias re-emits @mono-vue-root -> .mono/apps/mono-vue, plus adding a my-memo app entry on the host. Repointed src imports to @my-memo/*, ran mono:sync + mono:prepare.
Why: Mono Skills skips save for generic template names (Template Rule 1), so a real rename was needed. The circular host<->remote federation hardcodes @mono-vue-root; re-providing that alias via the base remote restores config loading without editing the host's federation import.
Kept the mono-vue base-remote federation (accepted trade-off)
Left mono-vue in apps[] rather than repointing the host's federation import to @my-memo-root.
Why: User chose the zero-extra-host-edit path. Trade-off: the app also federates the example branch's routes (/budget/alokasi, /master/user, /module-one/example) and merges that branch's config. The cleaner alternative (host import -> @my-memo-root, only /memo) was documented but not taken.
Files changed
| File | Operation | Note |
|---|---|---|
src/datas/tables/memo.ts | added | Memo interface, MemoCol columns, memoSearchExpr, memoCategories, MEMO_STORAGE_KEY |
src/stores/use-memo.ts | added | Pinia store useMemoMyMemo with localStorage CRUD + filtered computed + modal helpers; imports @my-memo/* |
src/pages/memo/index.vue | added | /memo list + search + table + mono-modal create/edit form using mono-* components; imports @my-memo/* |
mono.config.ts | modified | name 'mono-vue' -> 'my-memo'; added 'Memo' sidebar menu entry (/memo); added mono-vue base remote to apps[] so @mono-vue-root resolves after rename |
src/types/odata.d.ts | modified | QDefault import alias @mono-vue/* -> @my-memo/* |
.mono/apps/mono-host/mono.config.ts | modified | Host repo (mono-nuxt-host) gained a my-memo app entry; pulled via mono:sync. Host federation import still @mono-vue-root (resolved via the base remote). |
Commands
pnpm mono:prepare
pnpm mono:sync
pnpm run env:dev -- vue-tsc --noEmit
pnpm dev
pnpm run env:dev -- mono skills check
pnpm run env:dev -- mono skills save --app my-memo --dir .mono/skills/pending/<id>Validation
Tests: not run Build: passed