Skip to content

โ† All Skills Knowledge History

Move all memo page logic into the Pinia store (Rule 15)

completed๐Ÿ“ฆ my-memo๐Ÿ‘ค YCEJ9999๐Ÿ—“ 2026-06-21 12:00:00

memorule-15pinia-storethin-pageuseFileSystemAccess-in-storeprogrammatic-file-inputrefactor

Request

  • Every possible logic should live in /stores; the page is too heavy.
  • Create a Mono Skills entry.

Summary

Move all memo page logic into the Pinia store (Rule 15)

The /memo page had grown to ~160 lines of logic in <script setup> (file I/O, four import/export actions, fallback handlers, two hidden <input> refs, useMediaQuery, formatters). Rule 15 โ€” pages render, stores think, composables provide reusable helpers โ€” says that belongs in the store. After the refactor the page <script> is 22 lines (render-only) and use-memo.ts owns everything.

What moved into src/stores/use-memo.ts

  • useFileSystemAccess({ dataType: 'Text', โ€ฆ }) and useMediaQuery('(max-width: 640px)') โ€” yes, VueUse composables run fine inside a Pinia setup store (it's an effect scope), and both are SSR-safe (they guard window/document), which matters for the Nuxt host's prod SSR.
  • Actions: exportMemo / importMemo (prefill the form) / exportAll / importBatch.
  • Private helpers that dedupe the FSA-vs-fallback branching: readPickedText() (open) and saveText(text,name,okMsg) (save), plus applyImported(text).
  • Render glue so the template stays declarative: yn, fmtDate, onSearch(e) (reads the mono-input CustomEvent), clearSearch(), and an inputAccent computed for the paper tint.

The nice simplification: programmatic file-input fallback

The old page kept two hidden <input type="file"> elements + refs + change handlers just for non-Chromium import. In the store there's no template, so the fallback creates the input in JS:

function pickTextFile(): Promise<string | null> {
  return new Promise((resolve) => {
    const el = document.createElement('input')
    el.type = 'file'; el.accept = '.txt,text/plain'
    el.onchange = async () => { const f = el.files?.[0]; resolve(f ? await f.text() : null) }
    el.click() // cancel โ†’ no change event โ†’ promise stays pending (harmless)
  })
}

This removed both hidden inputs (and their refs/handlers) from the page entirely โ€” fewer moving parts, and all import paths funnel through one readPickedText().

Layering kept (per the user's choice)

Pure data transforms (serializeMemo / serializeMemos / parseMemos / memoFileName / backupFileName) stay in the use-memo-utils composable โ€” the store consumes it (const utils = useMemoUtilsMyMemo()). So: page renders โ†’ store thinks โ†’ composable provides the reusable helpers. Exactly Rule 15.

Page after refactor

Script = side-effect mono-helper/ui imports + static-data imports (memoCols, memoCategories, memoCategoryAccent โ€” data, not logic) + definePage + const store = useMemoMyMemo() + onMounted(() => store.load()). Every @mno-click / @mno-input calls a store method (store.exportAll(), store.onSearch($event), โ€ฆ); the modal sizing reads store.isSmallScreen; the paper tint reads store.inputAccent.

Gotcha

VueUse auto-imports (useFileSystemAccess, useMediaQuery) and the useMemoUtilsMyMemo composable all resolve inside the store file โ€” unplugin-auto-import scans src/stores too, same as it already did for useHelperMonoVue().

Files

  • src/stores/use-memo.ts โ€” gained the FSA instance, the four file actions, the dedup helpers, and the render-glue (onSearch/fmtDate/yn/inputAccent/isSmallScreen).
  • src/pages/memo/index.vue โ€” reduced to render-only; removed the hidden inputs and all functions.
  • src/composables/use-memo-utils.ts โ€” unchanged (now consumed by the store).

Outcome

successvite module transform + page-leftover grep /memo, page, store, and use-memo-utils.ts all transform 200 Page <script> is 22 lines (render-only); no fsa/useFileSystemAccess/fmtDate/val/fileInput left in the page Store now exposes exportMemo/importMemo/exportAll/importBatch/onSearch/inputAccent/fmtDate/yn/isSmallScreen + the private FSA helpers

Decisions

All orchestration/state/file-I/O lives in the store (Rule 15)

Moved FSA, the four import/export actions, fallbacks, useMediaQuery, and formatters from the page into use-memo.ts; the page only renders and calls store methods.

Why: Rule 15 (pages render, stores think) and the user's standing rule 'every possible logic on /stores'. Keeps logic reusable and the page declarative.

Pure serialize/parse stay in the use-memo-utils composable

Did NOT inline serialize/parse into the store; the store consumes useMemoUtilsMyMemo().

Why: User chose to keep the helper layer. Rule 15 puts reusable pure helpers in a use-<name>-utils composable consumed by the store.

Programmatic <input> for the non-Chromium import fallback

Create the file input via document.createElement in the store instead of keeping hidden <input> elements + template refs in the page.

Why: The store has no template; building the input in JS removes two hidden inputs/refs/handlers from the page and funnels all imports through one readPickedText().

VueUse composables inside the Pinia store are fine

useFileSystemAccess / useMediaQuery are instantiated in the store setup.

Why: Pinia setup stores are an effect scope; both composables are SSR-safe (guard window/document), so they work in the Nuxt host (SSR) and the remote (SPA). Auto-import resolves them in src/stores too.

Files changed

FileOperationNote
src/stores/use-memo.tsmodifiedAbsorbed page logic: useFileSystemAccess + useMediaQuery instances; exportMemo/importMemo/exportAll/importBatch; private readPickedText/saveText/pickTextFile (programmatic input fallback) + applyImported; render glue yn/fmtDate/onSearch/clearSearch/inputAccent. Consumes useMemoUtilsMyMemo.
src/pages/memo/index.vuemodifiedReduced to render-only (~22-line script): static-data imports + store + onMounted(load). All handlers call store methods; removed the two hidden <input> fallback elements, fsa, formatters, and val.
src/composables/use-memo-utils.tsmodifiedPure serialize/parse helpers โ€” now consumed by the store instead of the page (Rule 15 helper layer).

Commands

pnpm dev
curl /memo + page + store + use-memo-utils.ts (all 200)
MONO_SKILLS=true MONO_SKILLS_GITHUB_TOKEN=[REDACTED] pnpm exec mono skills save --app my-memo --dir .mono/skills/pending/<id>

Validation

Tests: not run Build: passed


โ† All Skills Knowledge History