docs(16-01): complete vue-journal-layer plan

- SUMMARY with the D-20/D-21 prerequisite contract
- USER-SETUP: both Gitea repos exist; the site repo is private (SSH)
This commit is contained in:
2026-10-09 18:09:36 +02:00
parent 7c5b8a8cb7
commit b53892c316
2 changed files with 347 additions and 0 deletions

View File

@@ -0,0 +1,326 @@
---
phase: 16-grzybyfunkcjonalne-pl-on-reusable-blog-views
plan: 01
subsystem: frontend (Nuxt layer)
tags: [nuxt, nuxt-layer, vue, i18n, nuxtjs-seo, sitemap, rss, pnpm-git-dependency, journal]
status: complete
requires:
- phase: 15-journal-plugin
provides: "anonymous Journal API /_journal/api/v1 (posts, posts/{slug}, categories, tags, rss)"
- phase: quick-261009-i6e
provides: "D-20 anonymous author endpoints /authors/{author} and /authors/{author}/posts"
- phase: quick-261009-mgk
provides: "D-21 category content field on GET /categories rows"
provides:
- "vue-journal-layer repo on git.golem15.com, tags v0.0.1 (spike) and v0.1.0 (release for 16-02)"
- "Nuxt module configKey journal: journal.routes, perPage, sort, relatedCount, rss; route names journal-index/post/category/tag/author"
- "typed anonymous-GET Journal client (createJournalClient) with include_empty=1 on categories"
- "gateJournalHtml: TS port of the Go FormatHTML three-pattern gate for every v-html sink"
- "--journal-* CSS tokens, overridable Journal* components, pl/en strings registered per enabled locale"
- "@nuxtjs/seo meta, canonicals keeping ?page=N, /api/__sitemap__/journal source, /rss.xml proxy"
- "testing/fixture-api.mjs, testing/nuxt-server.mjs, testing/smoke-ssr.mjs, scripts/check-git-install.sh"
affects: [16-02, 16-03, 16-04, 16-05]
actuals:
tokens: 27751 # chars/4 over the 54 tracked layer files, pnpm-lock.yaml excluded
tasks: 4
commits: 3 # git rev-list --count HEAD in vue-journal-layer (the repo started empty)
commits: 3
plan_head_before: null # vue-journal-layer was an empty repository; no base commit exists
plan_head_after: 783426c5d84c5d7c088f1e74b01c5240811c88d2
tech-stack:
added: ["vue-journal-layer (new repo): nuxt 4.4.8, vue 3.5.35, @nuxtjs/i18n 10.4.0, @nuxtjs/seo 5.2.1, @nuxt/fonts 0.14.0, typescript 5.9.3 (all already locked in vue-summercmsio-app)"]
patterns:
- "Layer pages live in app/journal-pages and are registered by extendPages from journal.routes; links go through useLocalePath over stable route names"
- "SSR fetches use the server-only apiBase and forward x-forwarded-for plus the peer address"
- "useAsyncData transforms gate and slim API rows (toPostView, toListPost) so unsafe HTML, raw content and translations never reach the payload"
- "Layer strings registered through the i18n:registerModule hook only for locales the app enables; the layer config declares no locales"
- "Built-server SSR smoke against a node:http fixture API with a line-based expectations file"
key-files:
created:
- ../vue-journal-layer/lib/journal-module.ts
- ../vue-journal-layer/nuxt.config.ts
- ../vue-journal-layer/app/utils/client.ts
- ../vue-journal-layer/app/utils/htmlGate.ts
- ../vue-journal-layer/app/utils/routes.ts
- ../vue-journal-layer/app/utils/view.ts
- ../vue-journal-layer/app/utils/seo.ts
- ../vue-journal-layer/app/utils/sitemap.ts
- ../vue-journal-layer/app/composables/useJournalApi.ts
- ../vue-journal-layer/app/journal-pages/PostPage.vue
- ../vue-journal-layer/server/api/__sitemap__/journal.ts
- ../vue-journal-layer/server/routes/rss.xml.ts
- ../vue-journal-layer/testing/smoke-ssr.mjs
- ../vue-journal-layer/testing/fixture-api.mjs
- ../vue-journal-layer/scripts/check-git-install.sh
- ../vue-journal-layer/README.md
modified: []
key-decisions:
- "vue-journal-layer pages gate and slim API data in useAsyncData transforms (toPostView/toListPost), so the SSR payload never carries an unsafe body, raw content or translations"
- "SSR, sitemap and RSS fetches forward x-forwarded-for with the Nuxt peer address appended, so the Go limiter's rightmost-untrusted-hop rule picks the real visitor"
- "vue-journal-layer tsconfig.json is self-contained (no extends of a generated .nuxt/tsconfig.json), because Vite reads it from a consumer's node_modules where no .nuxt exists"
- "The layer author page links posts by author.login (the only author key on post rows) and sets the canonical to the endpoint's slug URL"
- "The vue-grzybyfunkcjonalne-app site repo is private by the user's choice and is checked over SSH; only vue-journal-layer must be public (D-04)"
patterns-established:
- "Nuxt layer distribution: annotated git tag on git.golem15.com, consumer pins git+https://...#tag, lockfile pins the tag commit, no prepare script"
- "scripts/check-git-install.sh <tag> as the release gate for the layer"
requirements-completed: [SC-2, SC-3]
coverage:
- id: D1
description: "A scratch app installs vue-journal-layer anonymously from git tag v0.1.0 (and v0.0.1), pnpm-lock.yaml pins the tag commit, nuxt build succeeds and the SSR smoke passes (D-04)"
requirement: SC-2
verification:
- kind: integration
ref: "bash ../vue-journal-layer/scripts/check-git-install.sh v0.1.0 -> check-git-install: ok v0.1.0 783426c5d84c5d7c088f1e74b01c5240811c88d2"
status: pass
- kind: integration
ref: "bash ../vue-journal-layer/scripts/check-git-install.sh v0.0.1 -> check-git-install: ok v0.0.1 6f6792f51c3b437490a38ef3fb5e906447726139"
status: pass
human_judgment: false
- id: D2
description: "Post list with ?page=N pagination and 404s, post page (gated body, meta, featured image, siblings, related with latest fallback), category (include_empty=1, D-21 content, empty state, unknown 404), tag and author pages, all from journal.routes (D-05, D-06, D-21, D-24)"
requirement: SC-3
verification:
- kind: integration
ref: "node ../vue-journal-layer/testing/smoke-ssr.mjs --output ../vue-journal-layer/.playground/.output --fixtures ../vue-journal-layer/testing/fixtures/acme --expect ../vue-journal-layer/testing/expect-acme.txt -> smoke-ssr: ok (28 checks)"
status: pass
human_judgment: false
- id: D3
description: "SEO meta, canonicals that keep ?page=N, sitemap.xml from the Journal API built from journal.routes, /rss.xml as application/rss+xml (D-13)"
requirement: SC-3
verification:
- kind: integration
ref: "testing/expect-acme.txt lines for /rss.xml, /sitemap.xml, canonical and og:title, run by smoke-ssr.mjs (28 checks ok)"
status: pass
human_judgment: false
- id: D4
description: "pl/en strings register only for enabled locales: a pl-only app serves pl strings at /blog and 404s /en/blog and /pl/blog (D-16)"
requirement: SC-2
verification:
- kind: integration
ref: "temporary pl-only playground build + smoke-ssr.mjs with 4 expectations -> smoke-ssr: ok (4 checks); playground config restored"
status: pass
human_judgment: false
- id: D5
description: "The neutral token-driven base design looks clean on its own (D-03)"
verification: []
human_judgment: true
rationale: "Visual quality of the neutral design is a judgment call; the smoke only proves the markup and that components carry no hard-coded colours"
duration: 1h 34m
completed: 2026-10-09
---
# Phase 16 Plan 01: vue-journal-layer Summary
**A reusable Nuxt 4 layer, `vue-journal-layer` v0.1.0 on git.golem15.com, renders every Journal view from the anonymous Journal API. It covers post list, post, category with rich content, tag, author, RSS, SEO and sitemap. Paths come from `journal.routes`, styling from `--journal-*` tokens, and pl/en strings are registered per enabled locale. A scratch app installs the layer from the git tag and passes a 28-check SSR smoke.**
## Performance
- **Duration:** 1h 34m
- **Started:** 2026-10-09T14:33:15Z
- **Completed:** 2026-10-09T16:07:44Z
- **Tasks:** 4 (Task 1 was the resolved prerequisite gate)
- **Files modified:** 55 tracked files in `../vue-journal-layer` (new repo)
## Accomplishments
- **D-04 distribution proven end to end.** `check-git-install.sh` installs the layer anonymously from `git+https://git.golem15.com/golem15/vue-journal-layer.git#<tag>`. It then checks that `pnpm-lock.yaml` pins the tag's peeled commit, runs `nuxt build`, and runs the SSR smoke from `node_modules`. This passed for v0.0.1 (the spike) and for v0.1.0 (the release 16-02 consumes).
- **Configurable routes (D-06).** A local module (`lib/journal-module.ts`, configKey `journal`) registers `journal-index`, `journal-post`, `journal-category`, `journal-tag` and `journal-author` at the paths in `journal.routes`. Setting a route to `false` disables it, and a path missing its param fails the build with an error naming `journal.routes.<key>`.
- **Every Journal view (D-05, D-21, D-24):**
- The index paginates with `?page=N`. `abc`, `0` and out-of-range pages answer 404.
- Category pages render the D-21 `content` through the HTML gate above the list. An empty category answers 200, and an unknown one answers 404.
- Post pages show meta, the featured image, previous and next links, and related posts, falling back to the latest posts.
- Tag and author pages are included. An author resolves by slug or login, and a login URL's canonical points at the slug URL.
- **HTML gate (T-16-01).** Every v-html sink uses `gateJournalHtml`, which applies the three Go regexes. On top of that, page transforms gate and slim the data before it is serialized, so even the SSR payload never carries the unsafe body (the `notcontains=alert(` check proves this).
- **SEO (D-13).** Per-page `useSeoMeta`, `og:image` from the featured image, canonicals that keep `?page=N` (whitelist locked to `page`), a sitemap source that pages posts at `per_page=30` and builds every loc from `journal.routes`, and `/rss.xml` proxying the API feed.
- **i18n (D-16).** pl and en strings with identical key trees (21 leaves) are registered through `i18n:registerModule`, and only for locales the app enables. A pl-only build serves pl at `/blog`, and `/en/blog` answers 404.
## Task Commits
All code commits are in `../vue-journal-layer` (branch `main`, pushed):
1. **Task 1: Prerequisite gate.** Resolved by the human, and the orchestrator re-ran the gate (gate-ok). There was no commit. The contract is recorded below.
2. **Task 2: Tracer, git-tag install and SSR post page.** `6f6792f` (feat), tagged `v0.0.1`.
3. **Task 3: Post list, categories, pagination, related posts, tokens and pl/en strings.** `c408eeb` (feat).
4. **Task 4: Author and tag pages, SEO, sitemap, /rss.xml, README and v0.1.0.** `783426c` (feat), tagged `v0.1.0`.
**Plan metadata:** the docs commit in summercms.go (this SUMMARY).
Remote state: `refs/heads/main` 783426c, `refs/tags/v0.0.1` → 6f6792f, `refs/tags/v0.1.0` → 783426c (both are annotated tags).
## Prerequisite contract
This was read from `../sm-journal-plugin` at `3d533d4` (local `main`, not pushed) and from the quick-task SUMMARYs 261009-i6e and 261009-mgk. The plugin is the contract. This plan did not modify `../sm-journal-plugin`; its working tree shows only the pre-existing untracked `go.work.sum`.
### D-20 author endpoints (plugin commits 908ade9, 57a347c)
- **Routes.** `GET /_journal/api/v1/authors/{author}` and `GET /_journal/api/v1/authors/{author}/posts` are both anonymous and sit in the `throttle:journal-public-api` group. `{author}` is resolved in the handler, not by a route `Where`.
- **Resolution.** The value is trimmed first.
- It matches `backend_users.golem15_bloghub_author_slug` first, which works even when the author has no posts.
- Otherwise it matches `login`, but only when that user has at least one published post.
- Soft-deleted users never resolve.
- A malformed value always answers 404: empty, over 255 bytes, invalid UTF-8, or containing control characters.
- **Profile body.** `200 {"data":{"id":7,"slug":"jane-doe","first_name":"Jane","last_name":"Doe"}}`.
- It has exactly these four keys.
- `slug` is the author slug when set, otherwise the login.
- A missing name is `""`.
- **There is no public profile field beyond the names.** Bio, subtitle and avatar are not exposed; that is the `journal-author-profile-fields` todo.
- **Posts.** `/authors/{author}/posts` returns the `/posts` envelope `{data:[post rows], meta:{current_page,last_page,per_page,total}}` with `serializePost` rows.
- Query keys are `page` (default 1, clamped to at least 1), `per_page` (default 9, clamped to 1..30) and `sort` (the `/posts` allow-list, default `published_at desc`).
- Only published posts appear.
- **404 body.** `{"error":"Author not found"}` from both routes, with no other keys.
- **Post rows.** The `author` object on post rows is still `{id, first_name, last_name, login}` with no slug, so the layer links authors by `login` and canonicalises to `data.slug`.
### D-21 category content (plugin commits 740dcd0, f22d089, 3d533d4)
- **Key.** `content` appears on every `GET /_journal/api/v1/categories` row, children included, with and without `include_empty=1`.
- **Value.** It is an **HTML** string (not markdown) or `null`, stored and served as is. It is not translatable.
- **Writes.** The admin save path is gated by `classes.CheckHTML`, the same three patterns. 16-03's direct UPDATE bypasses that gate, so the layer gate stays mandatory.
- **Other keys** are unchanged: `id, name, slug, description, parent_id, nest_depth, post_count, children?`. Post rows' `categories[]` entries are still `{id, name, slug}`.
### Differences from the plan's assumed interfaces
- The author route parameter is `{author}` (a slug or a login), as assumed. The author posts route is `/authors/{author}/posts`, not `posts?author=`.
- The author object has no public profile fields beyond the names. `JournalAuthor` is `{id, slug, first_name, last_name}`, and `JournalAuthorCard` renders the name and offers a slot. No bio is rendered, so there is no third v-html sink yet.
- The category key is `content` as assumed, and it is HTML.
## Files Created/Modified
All of these are in `../vue-journal-layer`, a new repository:
- `package.json`, `pnpm-lock.yaml`, `pnpm-workspace.yaml`, `.nvmrc`, `.gitignore`, `tsconfig.json`: the package (`main: ./nuxt.config.ts`, nuxt as a dev and peer dependency, no `prepare` script).
- `nuxt.config.ts`: the layer config. It has absolute paths, `pages: true`, the modules declared once, runtime config, css, `seo.canonicalQueryWhitelist`, the sitemap source and no locales.
- `lib/journal-module.ts`: route registration, public runtime config and the i18n registration hook.
- `app/utils/{types,routes,client,htmlGate,view,pagination,seo,sitemap}.ts`: pure TS helpers that `node --test` can import.
- `app/composables/{useJournalApi,useJournalLinks}.ts`.
- `app/journal-pages/{PostIndexPage,PostPage,CategoryPage,TagPage,AuthorPage}.vue`.
- `app/components/Journal/{PostBody,PostCard,PostList,PostMeta,FeaturedImage,Pagination,RelatedPosts,IndexHeader,CategoryHeader,AuthorCard,RssLink}.vue`.
- `app/assets/css/{journal-tokens,journal-base}.css`, `app/app.vue`, `app/layouts/default.vue`.
- `i18n/locales/{pl,en}.json`.
- `server/routes/rss.xml.ts`, `server/api/__sitemap__/journal.ts`.
- `testing/{fixture-api,nuxt-server,smoke-ssr}.mjs`, `testing/fixtures/acme/*`, `testing/expect-acme.txt` (28 checks).
- `scripts/check-git-install.sh`, `.playground/{nuxt.config.ts,app/app.vue}`, `README.md`.
## Decisions Made
- **Payload shaping.** Pages run API rows through `toPostView` and `toListPost` in `useAsyncData` transforms. The payload drops `content` and `translations` and carries only gated `content_html`. This is a security and size win; see deviation 2.
- **Forwarding.** XFF forwarding appends the Nuxt peer address. Go reads the rightmost untrusted hop and trusts `127.0.0.1/32`, so behind nginx it still picks the visitor, and a Nuxt server reached directly cannot have its bucket spoofed by a client-supplied header.
- **Author links.** Authors are linked by `author.login`, because that is the only key on post rows. The author page canonicalises to the slug URL.
- **Featured images.** They carry intrinsic `width="1200" height="630"` attributes, and the rendered crop comes from the `--journal-image-ratio` token. Attributes cannot be set from CSS tokens; this is how the plan's wording was read.
- **Smoke checks.** The smoke additionally asserts that every categories request carried `include_empty=1`. The expectations also cover a draft post (404), the related-posts fallback, `/blog/author/jdoe` (login) and an exact canonical.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] The layer tsconfig.json could not extend `./.nuxt/tsconfig.json`**
- **Found during:** Task 2 (first playground build).
- **Issue:** `vite:vue` failed with `failed to resolve "extends":"./.nuxt/tsconfig.json"`. A layer has no `.nuxt` (only `.playground/.nuxt`), and neither does a consumer's `node_modules` copy.
- **Fix:** I wrote a self-contained `tsconfig.json` (ESNext, Bundler resolution, `allowImportingTsExtensions`, strict).
- **Files modified:** `../vue-journal-layer/tsconfig.json`.
- **Verification:** The playground build and both git-install runs pass.
- **Committed in:** 6f6792f.
**2. [Rule 2 - Missing critical] The SSR payload carried unsafe HTML**
- **Found during:** Task 2.
- **Issue:** `useAsyncData` serializes the raw API row into `__NUXT_DATA__`, including an unsafe `content_html`, plus `content` and `translations`. The plan's `notcontains=alert(` check could not pass that way, and the raw HTML would reach the browser.
- **Fix:** I added `app/utils/view.ts`, which provides `toPostView` (gates the body, sets `content_blocked`, drops `content` and `translations`, slims related posts) and `toListPost`. `JournalPostBody` gained a `blocked` prop. The category page gates `content` before it enters the payload.
- **Files modified:** `app/utils/view.ts`, `PostBody.vue`, all pages.
- **Verification:** The `/blog/unsafe-sample` smoke line passes (contains the blocked class, not `alert(`).
- **Committed in:** 6f6792f and c408eeb.
**3. [Rule 1 - Bug] ASI bug in the smoke-ssr.mjs option parser**
- **Found during:** Task 2.
- **Issue:** A line starting with `(` after a JSDoc cast was parsed as a call.
- **Fix:** I rewrote the parser to collect options into an object.
- **Committed in:** 6f6792f.
**4. [Rule 2 - Missing critical] The forwarded XFF lacked the peer address**
- **Found during:** Task 2.
- **Issue:** Forwarding the client header alone would let a client choose its own rate-limit bucket if Nuxt is reachable directly.
- **Fix:** `useJournalApi`, the sitemap source and `/rss.xml` append the peer address.
- **Verification:** The smoke forwarding assertion passes; the fixture recorded `203.0.113.7, 127.0.0.1`.
- **Committed in:** 6f6792f and 783426c.
**5. [Rule 3 - Blocking] Minimal index, category, tag and author pages were shipped in v0.0.1**
- **Found during:** Task 2.
- **Issue:** The module registers every route that is not `false`, so the build failed (ENOENT) without the other page files.
- **Fix:** I wrote simple working versions in Task 2. Tasks 3 and 4 replaced them with the full pages. These were not stubs: each fetched and rendered real data or answered 404.
- **Committed in:** 6f6792f.
**6. [Scope addition] `site.url`/`site.name` (`https://acme.test`, Acme Blog) in the playground and the scratch consumer config**
- **Issue:** Without them, canonicals carry the random smoke port, and titles show a literal `%siteName`.
- **Fix:** I added both, which makes the canonical assertions exact. The README documents that apps must set `site.url` and `site.name`.
- **Committed in:** 783426c.
**7. [Scope addition] TMPDIR guard in check-git-install.sh**
- **Issue:** Nuxt's layer include regex `(^|/)vue-journal-layer(/|$)` matches every path below a directory named `vue-journal-layer`. A scratch app there gets the auto-import transform on all dependencies, and the build fails with `Identifier "h" has already been declared` in `node-mock-http`. I hit this with a workaround TMPDIR.
- **Fix:** The script now refuses such a TMPDIR with a clear message, and the README notes it.
- **Committed in:** 783426c.
**8. [Scope addition] Extra i18n keys**
- **Issue:** The components needed `journal.pagination.label`, `journal.post.by`, `journal.tag.empty` and `journal.author.empty` beyond the plan's list.
- **Fix:** I added them to both locales. Parity is checked at 21 leaves.
- **Committed in:** c408eeb.
**9. [User decision, recorded per the orchestrator] The site repo is private**
- `golem15/vue-grzybyfunkcjonalne-app` is **private** by the user's choice. It is checked over SSH (`git ls-remote git@git.golem15.com:golem15/vue-grzybyfunkcjonalne-app.git`), not anonymous HTTPS as Task 1's verify and `user_setup` assumed.
- Only `vue-journal-layer` must be public (D-04 anonymous `git+https` install, verified by both git-install runs).
- The site app is consumed only as a D-08 submodule over SSH. 16-02 and 16-04 must use the SSH URL for it.
---
**Total deviations:** 9 (5 auto-fixed under Rules 1 to 3, 3 small scope additions, 1 user decision on repo visibility).
**Impact on plan:** All auto-fixes were needed for correctness, security or the build. Routes, components and option names match the plan's artifact list. The extra `app/utils/view.ts` module adds exported names; it changes none.
## Issues Encountered
- **The sandbox cannot write the `/tmp` pnpm store** (`ERR_SQLITE_ERROR attempt to write a readonly database`). The git-install runs used `TMPDIR=<session scratchpad>` with `npm_config_store_dir`/`pnpm_config_store_dir` pointed at the project-drive store. This is an environment workaround, and the script is unchanged by it.
- **The `@nuxt/fonts` build step retries provider metadata fetches** (fonts.bunny.net, api.fontshare.com) that the sandbox blocks. These are warnings only and the builds succeed. Apps that declare font families will fetch at build time.
- **nuxt-seo-utils warns** "treeShakeUseSeoMeta requires Unhead v3". This is harmless on Nuxt 4.4.8 with Unhead v2.
- **The D-16 pl-only check** first ran from a test app under `.data/`, which Nuxt ignores (`useJournalApi is not defined`). I re-ran it by temporarily switching `.playground` to pl-only, building, running the smoke, and restoring the file (no diff).
## Known Stubs
None. Every page fetches and renders real API data. `JournalAuthorCard` shows only the name because the D-20 endpoint exposes no profile fields; that gap is tracked by the existing todo `journal-author-profile-fields.md`, not by a stub.
## Threat Flags
None beyond the plan's threat model.
- `/rss.xml` and `/api/__sitemap__/journal` are new GET-only Nitro routes. They read from the runtime-config API base (T-16-02) and forward XFF (T-16-03).
- No write route is called: `grep -rnE "method: *'(POST|PUT|DELETE)'" app server` prints nothing.
## User Setup Required
Done before resume. See [16-USER-SETUP.md](./16-USER-SETUP.md): both Gitea repos exist; the layer repo is public and the site repo is private (SSH).
## Next Phase Readiness
- 16-02 can depend on `"vue-journal-layer": "git+https://git.golem15.com/golem15/vue-journal-layer.git#v0.1.0"` and set `journal.routes` to `{ index: '/blog', post: '/blog/:slug', category: '/:category', author: '/autor/:author', tag: false }`.
- **The site app must set `site.url` and `site.name`.**
- 16-02 can reuse `testing/fixture-api.mjs` and `testing/nuxt-server.mjs` from `node_modules/vue-journal-layer/testing/`.
- The 16-05 unit tests can target the pure utils in `app/utils/*.ts`: routes, client, htmlGate, pagination, view, seo and sitemap.
- **Before release, the plugin commits must be pushed.** The D-20 and D-21 commits in `../sm-journal-plugin` are local only, and the Go host build in 16-03 and 16-04 needs them pushed.
## Self-Check: PASSED
- FOUND: ../vue-journal-layer/{lib/journal-module.ts, app/utils/client.ts, app/utils/htmlGate.ts, app/utils/routes.ts, server/api/__sitemap__/journal.ts, scripts/check-git-install.sh, testing/smoke-ssr.mjs, README.md}
- FOUND: commits 6f6792f, c408eeb and 783426c in vue-journal-layer, and on origin/main
- FOUND: remote refs refs/tags/v0.0.1 and refs/tags/v0.1.0, and package.json version 0.1.0
- Plan verification:
- The Task 1 gate was gate-ok, re-run by the orchestrator with the site repo checked over SSH.
- `check-git-install.sh v0.1.0` printed `check-git-install: ok v0.1.0`.
- The playground smoke printed `smoke-ssr: ok (28 checks)`.
- The consuming-application name scan printed nothing.
---
*Phase: 16-grzybyfunkcjonalne-pl-on-reusable-blog-views*
*Completed: 2026-10-09*

View File

@@ -0,0 +1,21 @@
# Phase 16: User Setup
**Status:** Complete (verified 2026-10-09 during 16-01)
## git.golem15.com (Gitea 1.25.4)
Why: D-02, D-04 and D-08. The layer and the site app each live in their own repository.
| Task | Location | State |
|------|----------|-------|
| Create the repo `golem15/vue-journal-layer` as **public**, default branch `main` | https://git.golem15.com/repo/create (owner golem15) | Done. Anonymous `git ls-remote https://git.golem15.com/golem15/vue-journal-layer.git` lists `main`, `v0.0.1` and `v0.1.0`. |
| Create the repo `golem15/vue-grzybyfunkcjonalne-app`, default branch `main` | https://git.golem15.com/repo/create (owner golem15) | Done, as **private** by the user's choice. Check it with `git ls-remote git@git.golem15.com:golem15/vue-grzybyfunkcjonalne-app.git` (SSH). |
The layer repo has to be public, because consumers install it anonymously over `git+https` (D-04). The site app is only mounted as a submodule over SSH (D-08), so it can stay private.
## Verification
```bash
GIT_TERMINAL_PROMPT=0 git ls-remote https://git.golem15.com/golem15/vue-journal-layer.git
git ls-remote git@git.golem15.com:golem15/vue-grzybyfunkcjonalne-app.git
```