# 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 `