Skip to content

← All Skills Knowledge History

Memo CRUD page (localStorage) + renaming a mono-vue remote without breaking host federation

completedπŸ“¦ my-memoπŸ‘€ YCEJ9999πŸ—“ 2026-06-20 19:00:00

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 β€” Memo interface, MemoCol[] columns, memoSearchExpr, memoCategories, MEMO_STORAGE_KEY = 'my-memo:memos'. Same shape as user.ts (default-export { index: colMemo }).
  • src/stores/use-memo.ts β€” Pinia useMemoMyMemo / defineStore('memo-my-memo', ...). load() reads + JSON.parses the key (try/catch β†’ []); private persist() does localStorage.setItem; save() create/update (crypto.randomUUID(), new Date().toISOString()), remove(id), togglePin(id), modal helpers; a filtered computed does client-side search over memoSearchExpr (pinned-first, then updatedAt desc). Reuse useHelperMonoVue().notif for toasts.
  • src/pages/memo/index.vue β€” definePage({ meta:{ layout:'home', title:'Memo' }}), onMounted(() => store.load()). Single page; Add/Edit via mono-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) does import monoVueConfig from '@mono-vue-root/mono.config' and extends it back.
  • The @<name> / @<name>-root aliases are generated by monoAlias() from this repo's name. While name: 'mono-vue', @mono-vue-root WAS 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)

  1. This remote mono.config.ts: set name: 'my-memo', and add the base remote to apps[] so @mono-vue-root resolves 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' },
    ]
    
    monoAlias then emits @mono-vue / @mono-vue-root β†’ .mono/apps/mono-vue.
  2. Host repo (mono-nuxt-host): add a my-memo app entry pointing at the memo-apps branch (so the host federates this remote too).
  3. 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/*.
  4. pnpm mono:sync (re-pull host + first-time download of the mono-vue base), then pnpm 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

FileOperationNote
src/datas/tables/memo.tsaddedMemo interface, MemoCol columns, memoSearchExpr, memoCategories, MEMO_STORAGE_KEY
src/stores/use-memo.tsaddedPinia store useMemoMyMemo with localStorage CRUD + filtered computed + modal helpers; imports @my-memo/*
src/pages/memo/index.vueadded/memo list + search + table + mono-modal create/edit form using mono-* components; imports @my-memo/*
mono.config.tsmodifiedname '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.tsmodifiedQDefault import alias @mono-vue/* -> @my-memo/*
.mono/apps/mono-host/mono.config.tsmodifiedHost 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


← All Skills Knowledge History