# Mono Skills — Knowledge History Source: EJI-ICT/mono-skills (synced via `mono sync`). 12 session(s), newest first. ================================================================================ # gallery-apps: shrink grid thumbnails to 200x300 App: gallery-apps | Actor: YCEJ9999 | Date: 2026-07-02 16:10:03 | Status: completed Topics: gallery, Picsum, thumbnail resolution, bandwidth, lightbox Outcome: shipped — Changed thumbUrl default from 400x300 to 200x300 in src/datas/tables/gallery.ts. Updated the grid intrinsic size to width=200 height=300 in src/pages/gallery/index.vue. Verified the 200x300 Picsum URL returns 200 (~9.8KB); largeUrl (lightbox) unaffected since it passes explicit dimensions. ## User requests - Use a smaller thumbnail resolution for the gallery grid (like https://picsum.photos/200/300). ## Summary # gallery-apps — smaller grid thumbnails (200×300) ## What changed The gallery grid was requesting 400×300 renders per photo. Dropped the default to **200×300** so the infinite-scroll grid downloads much lighter images (a 200×300 Picsum render is ~10 KB). The full-size image in the lightbox is unaffected — it uses `largeUrl` (1200×800). ## Files - **`src/datas/tables/gallery.ts`** — `thumbUrl` default dimensions changed `400×300 → 200×300`. `largeUrl(p, w, h)` still passes explicit dimensions, so it's unaffected. - **`src/pages/gallery/index.vue`** — grid `` intrinsic size updated `width="400" height="300"` → `width="200" height="300"` to match the served image (avoids layout-shift / wrong aspect hint). ## Notes for future sessions - The grid uses `object-cover h-40`, so the CSS box is fixed regardless of the served resolution — shrinking `thumbUrl` only reduces bytes, it doesn't change the visible layout. - Thumbnail vs. lightbox resolution are cleanly separated: `thumbUrl` (200×300, grid) and `largeUrl` (1200×800, lightbox) both wrap Picsum's `/id/{id}/{w}/{h}` form in `src/datas/tables/gallery.ts`. ## Decisions - Keep the per-id Picsum URL, shrink only the dimensions — Kept the id-based Picsum form (/id/{id}/200/300) instead of the random-image form the user cited (https://picsum.photos/200/300). (Why: The random form returns a different photo each request; the grid must keep showing each photo's own image (and match the lightbox), so we only shrank the dimensions on the existing per-id URL.) - Match the intrinsic size to 200x300 — Updated the width/height attributes to 200x300 alongside the URL change. (Why: The intrinsic-size attributes are an aspect-ratio/layout hint for the browser; leaving them at 400x300 while serving 200x300 would give a mismatched aspect hint.) ## Files changed - src/datas/tables/gallery.ts [modified] — undefined - src/pages/gallery/index.vue [modified] — undefined ## Commands - mono skills check - mono skills read --app gallery-apps --type knowledge - curl https://picsum.photos/id/10/200/300 ================================================================================ # gallery-apps: image-only lightbox on the Picsum gallery App: gallery-apps | Actor: YCEJ9999 | Date: 2026-07-02 15:53:07 | Status: completed Topics: image lightbox, gallery, Picsum, Vue Teleport overlay, VueUse keyboard + scroll lock, unplugin-vue-components auto-import Outcome: shipped — Added src/components/GalleryLightbox.vue (Teleport overlay, keyboard nav, scroll lock). Added largeUrl() helper to src/datas/tables/gallery.ts (reuses thumbUrl). Wired lightbox into src/pages/gallery/index.vue via v-model:index and @click.prevent. vue-tsc clean for the changed files; Vite compiled all three modules (HTTP 200), VueUse auto-imports resolved. ## User requests - Clicking a gallery thumbnail should open an image-only popup: a full-screen dark overlay with the large image centered, closable by backdrop click or Esc. - Add caption (author + dimensions), prev/next navigation, and a source/full-res link. - Run the Mono Skills read+save workflow around the task. ## Summary # gallery-apps — image-only lightbox on the Picsum gallery ## What was built Clicking a thumbnail on `/gallery` now opens an **image-only lightbox** instead of navigating away to the Unsplash source page. The lightbox is a full-screen dark overlay (`bg-black/90`) with the large image centered; it closes on backdrop click, the ✕ button, or **Esc**. It also carries minimal overlaid chrome: a caption (author + `width×height`), prev/next navigation across the loaded photos (on-screen arrows + **←/→** keys, hidden at the ends), and links to the source page (`p.url`) and full-resolution image (`p.download_url`). ## Files - **`src/components/GalleryLightbox.vue`** (new) — the lightbox. `` + `v-if`, props `photos: PicsumPhoto[]` and `index: number | null` (`v-model:index`), emits `update:index` + `close`. Keyboard via VueUse `onKeyStroke` (Escape/ArrowLeft/ArrowRight, guarded to only act while open); background scroll frozen via `useScrollLock(document.body)` toggled by a `watch` on the open photo. Backdrop uses `@click.self="close"`; chrome/image wrapped with `@click.stop`. - **`src/datas/tables/gallery.ts`** — added `largeUrl(p, w=1200, h=800)` that reuses `thumbUrl`'s `/id/{id}/{w}/{h}` form, so the lightbox serves a bounded image rather than the multi-MB original. - **`src/pages/gallery/index.vue`** — added `activeIndex = ref(null)`; the grid item keeps its `` (right-click/no-JS still works) but intercepts left click with `@click.prevent="activeIndex = i"` (`v-for="(p, i) in store.photos"`); renders ``. ## Key facts for future sessions - **`src/components/` is the `unplugin-vue-components` auto-import root** — components there register globally with no manual import in the page (verified: Vite injected the import automatically). - **VueUse is auto-imported** project-wide (`onKeyStroke`, `useScrollLock`, `useIntersectionObserver` all resolve from `@vueuse/core` with no explicit import). - `PicsumPhoto` already carries everything the lightbox needs (`author`, `width`, `height`, `url`, `download_url`) — no store changes were required; the lightbox pages through `store.photos`. - `mono-modal` (`mono-helper/ui/modal`) exists and is the project's modal convention, but it always renders a padded card — not suitable for an edge-to-edge image-only lightbox (see decisions). - Pre-existing unrelated typecheck error: `.mono/apps/mono-host/app/composables/use-utils.ts` references `import.meta.client` (Nuxt) → `TS2339` under this app's vue-tsc. Not caused here. ## Decisions - Custom Teleport lightbox instead of mono-modal — Built a custom Teleport overlay component (GalleryLightbox.vue) instead of reusing the mono-modal web component. (Why: mono-modal always renders a padded modal card; the requested design is an edge-to-edge, image-only lightbox. A small dedicated Teleport overlay is cleaner and gives full control over backdrop, arrows, and caption. All needed behavior (keyboard, scroll-lock) is already available via auto-imported VueUse.) - Keep the anchor, intercept left-click with @click.prevent — Kept the grid item as an but intercept the left click with @click.prevent to open the lightbox. (Why: Preserves right-click 'open in new tab', middle-click, and no-JS graceful degradation to the source page, while a normal left click opens the in-app lightbox.) - Bounded 1200x800 largeUrl over the full original — Added a largeUrl() helper that reuses thumbUrl's /id/{id}/{w}/{h} form (1200x800) rather than loading p.download_url (the full original). (Why: Picsum originals are multi-megapixel/multi-MB; a bounded 1200x800 render is crisp enough for the lightbox and much faster. The full-res download_url is still offered as an explicit link.) - Guard the global VueUse key handlers on open state — Registered global VueUse onKeyStroke handlers but guarded each to only act while a photo is open. (Why: The component is always mounted (rendered in the page), so the key listeners are always live; guarding on the open state prevents Esc/arrows from firing when the lightbox is closed.) ## Files changed - src/components/GalleryLightbox.vue [added] — undefined - src/datas/tables/gallery.ts [modified] — undefined - src/pages/gallery/index.vue [modified] — undefined ## Commands - mono skills check - mono skills read --app gallery-apps --type knowledge - mono skills search --app gallery-apps --query "gallery lightbox image popup" - npx vue-tsc --noEmit - npm run dev ## Validation - Tests: not run - Build: passed ================================================================================ # gallery-apps standalone app: Picsum infinite-scroll gallery App: gallery-apps | Actor: YCEJ9999 | Date: 2026-06-29 07:00:00 | Status: completed Topics: gallery, infinite-scroll, picsum, monoFetch, restful, cors, app-rename, mono-skills ## Summary # gallery-apps — standalone Picsum infinite-scroll app This app (`gallery-apps`, on the branch of the same name) is a Vue/Vite mono remote whose single feature is an image gallery at `/gallery` that loads photos from the public Picsum API with infinite scroll. It started as a gallery page added on the `memo-apps` branch, then was split off into its own mono app: the memo feature was removed and the app was renamed `my-memo` → `gallery-apps`. ## Feature stack (Rule 15) - `mono.config.ts` — `name: 'gallery-apps'`; `Photos` restful endpoint (`https://picsum.photos/v2`) in `fetching.api`; menu = single `Gallery` entry. - `src/datas/tables/gallery.ts` — `PicsumPhoto` type, `GALLERY_PAGE_SIZE = 30`, `thumbUrl(p, w, h)` (`/id/{id}/{w}/{h}`) so the grid loads thumbnails, not originals. - `src/stores/use-gallery.ts` — `useGalleryGalleryApps` store (`photos / page / loading / hasMore / error`, `loadMore()` / `reset()`). - `src/pages/gallery/index.vue` — thin page; responsive UnoCSS grid + a bottom sentinel watched by `useIntersectionObserver` (rootMargin 300px). ## Reusable gotchas 1. **`monoFetch` returns a bare array in `res.all`, not `res.data`.** `useNormalFetch` returns `{ data: responseData?.data, all: responseData, ... }`; Picsum returns a bare array, so read `res.all`. (For non-`{data}`-shaped REST responses, use `res.all`.) 2. **`monoFetch` + a public API can be CORS-blocked.** It always sends `Content-Type: application/json` (forces a preflight) and attaches `Authorization=[REDACTED] when a `MONO_tokenRefresh` cookie exists. Picsum's preflight allows `content-type` but NOT `authorization`, so an authenticated session would be blocked. The store tries `monoFetch` first (base URL via `monoRestBaseUrl('Photos')`) and falls back to native `fetch`. 3. **Renaming a mono app is cascading.** App id = `mono.config.ts` `name` → drives `@/*` aliases, must match `package.json` `name`, and is the store-export/key suffix (Rule 8: `useGalleryMyMemo` → `useGalleryGalleryApps`, key `gallery-gallery-apps`). After editing the two name fields, run `mono prepare` to regenerate `.mono/tsconfig.json` aliases; restart Vite (delete `src/auto-imports.d.ts` first) so the auto-import dts re-scans cleanly — it merges rather than pruning deleted exports on a warm start. 4. **MONO Skills is app-scoped, immutable, no CLI delete.** `save` is skipped while the app uses a template-default name (incl. `mono-vue-remote`), so rename before saving. To move a session between apps, delete the files via the GitHub contents API and re-save under the new app id. (This session was moved my-memo → gallery-apps that way.) ## Verification - `vue-tsc --noEmit` clean for app files (only the pre-existing host `import.meta.client` error remains); Vite dev serves `/gallery`, `/memo` is gone, auto-imports show only `useGalleryGalleryApps`; branch `gallery-apps` pushed to origin. ## Decisions - Standalone gallery-apps on a new branch — Split the gallery into its own mono app (gallery-apps) on a new branch instead of keeping it on memo-apps. (Why: The gallery is unrelated to the memo app; a standalone app keeps concerns and skills history separate. Reusable: A 'new app' = new branch off the app with the full foundation + rename the app identity; main lacked the foundation so branching off it was not viable.) - monoFetch (res.all) with a native-fetch CORS fallback — Fetch via monoFetch with a Photos restful config entry; read the array from res.all; native fetch fallback. (Why: mono-utils/fetching is the sanctioned layer; Picsum returns a bare array (lands in res.all) and rejects a CORS preflight carrying the Authorization header monoFetch attaches when a session cookie exists. Reusable: For public/no-auth third-party REST via monoFetch: read res.all, and guard for auth-header CORS rejection with a native fetch fallback (base URL from monoRestBaseUrl).) - Bottom-sentinel infinite scroll via useIntersectionObserver — Infinite scroll via @vueuse/core useIntersectionObserver on a bottom sentinel (rootMargin 300px). (Why: Fits a grid + thin-page/store; stops when a page returns fewer than pageSize items. Reusable: Bottom-sentinel + useIntersectionObserver is the grid infinite-scroll pattern.) - Rename the app via mono.config + package.json + mono prepare — Renamed app my-memo -> gallery-apps by editing mono.config name + package.json name, then mono prepare. (Why: App id drives @/* aliases, package identity, and store-export/key suffix (Rule 8). Reusable: After a rename, run mono prepare to regen .mono/tsconfig aliases, and delete src/auto-imports.d.ts before restarting Vite so the auto-import dts re-scans cleanly (it does not prune deleted exports on a warm start).) - Move a skills session across apps via the GitHub contents API — Moved this skills session from app my-memo to gallery-apps by deleting the 5 files via the GitHub contents API and re-saving. (Why: Skills sessions are app-scoped and immutable with no CLI delete; the session was first saved under the old app id. Reusable: To move a skills session between apps: GET each file's SHA and DELETE via the contents API (touch only that session folder), then mono skills save under the new app id.) ## Files changed - mono.config.ts [modified] — name my-memo -> gallery-apps; Photos restful endpoint; menu reduced to Gallery - package.json [modified] — name my-memo -> gallery-apps (must match mono.config name) - src/datas/tables/gallery.ts [added] — PicsumPhoto type, GALLERY_PAGE_SIZE, thumbUrl helper - src/stores/use-gallery.ts [added] — useGalleryGalleryApps store: loadMore() via monoFetch (res.all) + native fetch fallback - src/pages/gallery/index.vue [added] — Gallery page: UnoCSS grid + useIntersectionObserver infinite scroll - src/types/odata.d.ts [modified] — @my-memo -> @gallery-apps alias - src/pages/memo/, src/stores/use-memo.ts, src/composables/use-memo-utils.ts, src/datas/tables/memo.ts, src/assets/memo-paper.css [deleted] — removed the entire memo feature ================================================================================ # Memo: mono-select type picker, mono-dropdown row-actions menu, sticky Aksi column App: my-memo | Actor: YCEJ9999 | Date: 2026-06-21 16:00:00 | Status: completed Topics: memo, mono-select, mono-dropdown, row-actions-menu, sticky-table-column, mono-ui, rule-13 Outcome: success — vite module transform + wiring grep /memo, page, store, datas transform 200 Page wires mono-select (type picker), mono-dropdown (row actions), actionMenuId/onTypeChange, memo-act, sticky right-0; the old .memo-type-toggle is gone UnoCSS generates i-mdi-dots-vertical Component APIs taken from the mono-helper vue/ demos (slot=main/body, :items.prop + key-value/display-value, $event.detail.modelValue) ## User requests - Change the type option in the modal to use mono-select. - Collapse the row action buttons into one menu icon that opens a dropdown of 3 items (Sematkan, Ubah, Hapus) so the action column stays minimal width. - Make the Aksi column sticky. - Add a Mono Skills entry. ## Summary # Memo: `mono-select` type picker + `mono-dropdown` row actions + sticky Aksi column Three table/form refinements on the typed-memo feature. The component APIs below were taken from the **mono-helper `demos/*/vue/` examples** (the authoritative source — the llms doc only sketches them). ## `mono-select` (the type picker) Replaced the segmented Teks/Checklist toggle with a select: ```html ``` - `:items.prop` takes a plain `[{ value, label }]` array (DOM property, not attribute). `key-value` / `display-value` map which field is the value vs the label. - The change event hands you the selected **value** at `$event.detail.modelValue`. - `memoTypeOptions = memoTypes.map(t => ({ value: t, label: memoTypeLabel[t] }))` lives in `datas` (Rule 9). `store.onTypeChange(e)` reads `detail.modelValue` and calls the existing `setType(v)` so the content⇄items migration still runs. ## `mono-dropdown` (row actions menu) Collapsed the 3 buttons (Sematkan/Ubah/Hapus) into one ⋮ icon that opens a menu — keeps the Aksi column narrow: ```html …3 buttons… ``` - **Slots:** `main` = the trigger, `body` = the panel. Default `trigger="click"`; built-in flip/shift. - **Controlled single-open:** bind `model-value` to `actionMenuId === row.id` and sync via `mno-open`/`mno-close`. Each action button calls the store method **and** `closeActionMenu()`. This matters because **Sematkan re-sorts the row** (pinned-first) — an uncontrolled menu would linger on a row that just jumped. Only one row's menu is open at a time. - The `body` items are plain light-DOM `` (icon + label, hover, danger variant) styled in `memo-paper.css` — fully styleable, unlike a shadow-DOM menu. ## Sticky Aksi column The table wrapper already has `overflow-x-auto`; with the extra Tipe column it can scroll. Pin the action column to the right: - header ``: `sticky right-0 z-10 bg-gray-50 border-l border-gray-200`. - each row ``: `sticky right-0 z-10 bg-white border-l border-gray-100 memo-aksi`, where `.memo-aksi` adds a soft left shadow so it reads as a pinned column. - The sticky cell needs an opaque background (to cover scrolled content), so the row `hover:bg-gray-50` doesn't tint it — the divider/shadow separates it instead. ## Rule 13 `mono-select` / `mono-dropdown` are used directly (they sit in the table/modal header, not on the custom lined paper, so no native-CSS workaround is needed). Only the dropdown's light-DOM menu items use page CSS. ## Files - `src/datas/tables/memo.ts` (memoTypeOptions), `src/stores/use-memo.ts` (onTypeChange, actionMenuId, closeActionMenu), `src/pages/memo/index.vue`, `src/assets/memo-paper.css` (.memo-actions/.memo-act, .memo-aksi; removed the old .memo-type-toggle rules). ## Decisions - Controlled single-open dropdown via actionMenuId — Bind mono-dropdown model-value to (actionMenuId === row.id), sync mno-open/mno-close, and close on every action. (Why: Sematkan re-sorts the row to the top (pinned-first); an uncontrolled menu would stay open over a row that jumped. Controlled state also guarantees only one row menu open at a time.) - Light-DOM buttons in the dropdown body, styled in page CSS — The mono-dropdown `body` slot holds plain items, not a shadow-DOM menu component. (Why: Light-DOM items are fully styleable (hover, danger color, icon+label) and keep logic in the store; the dropdown only provides positioning/open-state.) - Sticky Aksi column needs an opaque background — Sticky th/td use solid bg (bg-gray-50 / bg-white) + left border + soft shadow; the row hover tint doesn't reach the sticky cell. (Why: position:sticky cells must paint over horizontally-scrolled content, so they can't be transparent. The divider/shadow conveys the pinned column instead of the hover tint.) - Component APIs sourced from the demos, not the llms doc — Used the mono-helper demos/*/vue/ examples for the exact mono-select / mono-dropdown slots, props, and event detail shape. (Why: The llms-full.txt only sketches these components; the vue demos are the authoritative, copy-pasteable usage (slot=main/body, :items.prop, $event.detail.modelValue).) ## Files changed - src/datas/tables/memo.ts [modified] — Added memoTypeOptions ([{value,label}]) for the mono-select type picker - src/stores/use-memo.ts [modified] — onTypeChange(e) -> setType(detail.modelValue); actionMenuId ref + closeActionMenu() (controlled single-open row menu); exported all - src/pages/memo/index.vue [modified] — Type toggle -> mono-select; 3 action buttons -> one mono-dropdown menu (slot main/body, controlled); Aksi th/td made sticky right-0; imports mono-helper/ui/select + dropdown + memoTypeOptions - src/assets/memo-paper.css [modified] — Added .memo-actions/.memo-act(.--danger) menu styles + .memo-aksi sticky shadow; removed unused .memo-type-toggle/.memo-type rules ## Commands - pnpm dev - curl /memo + page + store + datas (all 200) - MONO_SKILLS=true MONO_SKILLS_GITHUB_TOKEN=[REDACTED] pnpm exec mono skills save --app my-memo --dir .mono/skills/pending/ ## Validation - Tests: not run - Build: passed ================================================================================ # Memo types: text | checklist (with checklist editor + .txt round-trip) App: my-memo | Actor: YCEJ9999 | Date: 2026-06-21 14:00:00 | Status: completed Topics: memo, memo-type, checklist, localStorage-migration, txt-round-trip, notebook-paper-ui, store-centric Outcome: success — vite module transform + standalone round-trip test /memo, page, store, use-memo-utils.ts, datas all transform 200 UnoCSS generates i-mdi-format-list-checks / i-mdi-text / i-mdi-close Round-trip test passed: checklist serializes to 'Tipe: checklist' + '- [ ] '/'- [x] ' lines and parses back with item text + done state; text memos round-trip; text->checklist migration strips '1.'/'-' bullets ## User requests - Give memos a type: text | checklist; the current memo is plain text. - Checklist example: a shopping list — '5kg Tomato', '2kg apple' — with checkable items. - Flag the type in the table (text | checklist). - The form must show a checklist-style editor for checklist memos. - Add a Mono Skills entry. ## Summary # Memo types: `text` | `checklist` Added a `type` to memos so a memo is either free **text** (the original) or a **checklist** of checkable items (e.g. a shopping list: `5kg Tomato`, `2kg apple`). The table flags the type; the notebook-paper form shows a checklist editor when the type is checklist. Fully backward-compatible and round-trips through the existing `.txt` import/export. ## Data model (`src/datas/tables/memo.ts`) - `type MemoType = 'text' | 'checklist'`; `interface ChecklistItem { text: string; done: boolean }`. - `Memo` gains `type: MemoType` and `items: ChecklistItem[]` — text memos use `content`, checklist memos use `items` (kept mutually exclusive: on save, text → `items: []`, checklist → `content: ''`). - `memoTypes` + `memoTypeLabel` (Teks/Checklist); a `{ field: 'type', caption: 'Tipe' }` table column. ## Backward compatibility (the important bit) Memos saved before this feature have no `type`/`items`. `load()` runs a `normalize()` over every record: `type ??= 'text'`, `items = Array.isArray(items) ? items : []`. So old localStorage data keeps working — no migration step, no crashes on `m.items.some(...)`. ## Store logic (`src/stores/use-memo.ts`, Rule 15) - `setType(t)` **migrates** between representations so the toggle "just works": - text → checklist: split non-empty `content` lines into items, stripping `1.` / `- ` / `*` bullets (`/^\s*(?:[-*]|\d+[.)])\s*/`). - checklist → text: join item texts back into lines. - `addInputItem()` / `removeInputItem(i)` / `toggleInputItem(i)` edit `input.items`; item text uses plain `v-model` on `store.input.items[i].text`. - `save()` canonicalizes by type and drops blank-text items. `filtered` also searches item text. `applyImported()` copies `type` + `items`. `exportMemo()` empty-guard also considers items. - `checklistProgress(m) → { done, total }` for the table badge. ## `.txt` round-trip (`use-memo-utils.ts`) The block gained a `Tipe: text|checklist` header. Checklist bodies serialize as **markdown checkboxes**, one per line — exactly the user's list: ``` Tipe: checklist ----- ISI ----- - [ ] 5kg Tomato - [x] 2kg apple ``` `parseMemos` reads `Tipe` (default `text`) and parses `^\s*-\s*\[( |x)\]\s?(.*)$` into items (done from `x`); text memos keep `content`. Plain-text imports default to `type: 'text'`. A standalone test confirmed full round-trip incl. done-state. ## UI (`src/pages/memo/index.vue` + `src/assets/memo-paper.css`) - A **type toggle** (Teks | Checklist segmented pills) above the title; clicking calls `setType`. - `type==='text'` → the existing lined textarea. `type==='checklist'` → a **checklist editor** on the ruled paper: each row = native checkbox (`accent-color: var(--memo-accent)`) + a borderless text input (Enter adds a new item) + a hover ✕ remove; a "+ Tambah item" button; done items get a strike-through. Table shows a type badge with progress (e.g. "Checklist · 1/2"). - **Rule 13 note:** native checkbox + input (styled in the page-local `memo-paper.css`), not `` — the form is a custom notebook-paper surface where every field is native so it can sit on the ruled lines (shadow-DOM `mono-*` can't be styled into that). This is Rule 13's native-CSS-in-`src/assets` fallback, consistent with the title/content/category fields. ## Files - `src/datas/tables/memo.ts`, `src/stores/use-memo.ts`, `src/composables/use-memo-utils.ts`, `src/pages/memo/index.vue`, `src/assets/memo-paper.css`. ## Decisions - type + items[] on Memo (mutually exclusive bodies) — Added type: 'text'|'checklist' and items: ChecklistItem[]. On save, text → items:[], checklist → content:''. (Why: Keeps each type's body canonical and avoids stale data; the table/search/export branch on type cleanly.) - Normalize old records on load (no migration step) — load() backfills type='text' and items=[] for any record missing them. (Why: Existing localStorage memos predate the feature; normalizing on read keeps them working and prevents undefined.items crashes.) - Smart type-toggle migration — setType converts content lines ⇄ items (stripping 1./- bullets when going to checklist). (Why: The user's flow is 'list my checklist'; toggling should turn their typed lines into checkable items (and back), not lose data.) - Markdown checkboxes in the .txt round-trip — Serialize checklist items as '- [ ]'/'- [x]' under a 'Tipe: checklist' header; parse them back. (Why: Human-readable, standard, and preserves done-state across export/import (and arbitrary .txt with such lines could be imported).) - Native checkbox/input styled in memo-paper.css (Rule 13 fallback) — Did not use ; used native elements styled in the page-local CSS. (Why: The memo form is a custom notebook-paper surface; shadow-DOM mono-* can't be styled onto the ruled lines. Consistent with the existing native title/content/category fields; Rule 13 permits native CSS when components/UnoCSS can't express the styling.) ## Files changed - src/datas/tables/memo.ts [modified] — MemoType + ChecklistItem; Memo gains type + items; memoTypes/memoTypeLabel; 'Tipe' table column - src/stores/use-memo.ts [modified] — blank() type/items; load() normalize old records; setType migration; add/remove/toggle item; save canonicalize+trim; filtered searches items; applyImported copies type/items; exportMemo guard; checklistProgress - src/composables/use-memo-utils.ts [modified] — Serialize 'Tipe' + checklist body as - [ ]/- [x]; parse type + items; plain-text fallback -> type text - src/pages/memo/index.vue [modified] — Type toggle; conditional textarea vs checklist editor; table type badge + progress; import memoTypes/memoTypeLabel - src/assets/memo-paper.css [modified] — memo-type-toggle; checklist rows on ruled lines (checkbox/input/remove, is-done strike); memo-add-item; small-screen flex-grow ## Commands - pnpm dev - curl /memo + page + store + utils + datas (all 200) - node round-trip + migration test (all passed) - MONO_SKILLS=true MONO_SKILLS_GITHUB_TOKEN=[REDACTED] pnpm exec mono skills save --app my-memo --dir .mono/skills/pending/ ## Validation - Tests: not run - Build: passed ================================================================================ # Move all memo page logic into the Pinia store (Rule 15) App: my-memo | Actor: YCEJ9999 | Date: 2026-06-21 12:00:00 | Status: completed Topics: memo, rule-15, pinia-store, thin-page, useFileSystemAccess-in-store, programmatic-file-input, refactor Outcome: success — vite module transform + page-leftover grep /memo, page, store, and use-memo-utils.ts all transform 200 Page