docs(33-11): complete auto-links, internal links and WebP variants plan
This commit is contained in:
@@ -0,0 +1,389 @@
|
||||
---
|
||||
phase: 33-seo-output-audit-and-content
|
||||
plan: 11
|
||||
subsystem: seo, media
|
||||
status: complete
|
||||
tags: [sm-seo-plugin, auto-links, internal-links, e10, mcp, lagoon, attach, webp, gen2brain, sm-journal-plugin, conga, image-variants, vue-journal-layer, picture, srcset]
|
||||
|
||||
requires:
|
||||
- phase: 33-seo-output-audit-and-content
|
||||
provides: "33-05 Links tab and the four-place settings pattern; 33-06 element bundle, the seo_graph widget pattern and the gen2brain/webp approval (v0.6.4); 33-09 vue-seo-layer autolink(), the consumer of autolinks/autolink_max; 33-10 crawl pages (main text) and links, the toolbar action operation kind"
|
||||
provides:
|
||||
- "golem15_seo_autolinks and settings autolink_max (migration 202610280007); ok resolve answers carry autolinks [{phrase, url}] and autolink_max"
|
||||
- "SEO › Auto-links (golem15.seo.autolinks) with Add focus keyphrases; /_seo/api/v1/autolinks routes; MCP seo_list_autolinks, seo_create_autolink, seo_update_autolink, seo_delete_autolinks, seo_autolinks_from_keyphrases"
|
||||
- "E10 golem15-seo-links (widget action seo_links) and GET /_seo/api/v1/links; MCP seo_get_link_suggestions"
|
||||
- "Framework: attach.VariantKey and package modules/lagoon/attach/webp (Encode, Variant, Quality, Format, ContentType); github.com/gen2brain/webp v0.6.4"
|
||||
- "sm-journal-plugin: golem15_journal_image_variants (migration 202610280002), job golem15.journal.image_variants, journal:image-variants [--all], config golem15.journal.image_widths, featured_images[].variants"
|
||||
- "vue-journal-layer: app/utils/srcset.ts (imageSrcsets) and <picture> with WebP in JournalFeaturedImage"
|
||||
affects: [33-12, 33-13, 33-14]
|
||||
|
||||
actuals:
|
||||
tokens: 46000
|
||||
tasks: 4
|
||||
commits: 2
|
||||
plan_head_before: 3bfdc8616e8bda172a5c835d3c6ac30548c4616a
|
||||
plan_head_after: 5c07224
|
||||
sibling_repos:
|
||||
sm-seo-plugin: {head_before: b553fc2, head_after: 49cc241, commits: 3}
|
||||
sm-journal-plugin: {head_before: ee3cb26, head_after: 4f5bd19, commits: 1}
|
||||
vue-journal-layer: {head_before: adcea4d, head_after: 1080a3f, commits: 1}
|
||||
|
||||
tech-stack:
|
||||
added:
|
||||
- "github.com/gen2brain/webp v0.6.4 (framework go.mod; only modules/lagoon/attach/webp imports it; its one dependency github.com/ebitengine/purego v0.10.1 was already in the module graph)"
|
||||
patterns:
|
||||
- "A generic media helper lives in a framework sub-package so that only binaries that import it link the encoder (attach/webp next to attach)"
|
||||
- "Auto-link writes record history under subject autolink, and its purge slug is * (a phrase may appear in any text)"
|
||||
- "A widget's suggestions read the last finished crawl; phrase matching shares crawl's normal form (classes.NormalText)"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- ../sm-seo-plugin/models/autolink.go
|
||||
- ../sm-seo-plugin/models/autolink/columns.yaml
|
||||
- ../sm-seo-plugin/models/autolink/fields.yaml
|
||||
- ../sm-seo-plugin/updates/202610280007_create_golem15_seo_autolinks.go
|
||||
- ../sm-seo-plugin/classes/autolinks.go
|
||||
- ../sm-seo-plugin/classes/linkgraph.go
|
||||
- ../sm-seo-plugin/controllers/autolinks.go
|
||||
- ../sm-seo-plugin/controllers/autolinks/config_list.yaml
|
||||
- ../sm-seo-plugin/controllers/autolinks/config_form.yaml
|
||||
- ../sm-seo-plugin/controllers/autolinks/config_filter.yaml
|
||||
- ../sm-seo-plugin/controllers/api/autolinks.go
|
||||
- ../sm-seo-plugin/controllers/api/links.go
|
||||
- ../sm-seo-plugin/assets/src/elements/links.ts
|
||||
- ../sm-seo-plugin/tests/elements/links.test.ts
|
||||
- ../sm-seo-plugin/linking_test.go
|
||||
- .planning/notes/2026-10-28-webp-gen2brain.md
|
||||
- modules/lagoon/attach/variant.go
|
||||
- modules/lagoon/attach/webp/webp.go
|
||||
- modules/lagoon/attach/webp/webp_smoke_test.go
|
||||
- modules/lagoon/attach/webp/export_test.go
|
||||
- ../sm-journal-plugin/classes/image_variants.go
|
||||
- ../sm-journal-plugin/console/image_variants.go
|
||||
- ../sm-journal-plugin/models/image_variant.go
|
||||
- ../sm-journal-plugin/updates/202610280002_create_golem15_journal_image_variants.go
|
||||
- ../sm-journal-plugin/jobs.go
|
||||
- ../vue-journal-layer/app/utils/srcset.ts
|
||||
- ../vue-journal-layer/tests/srcset.test.ts
|
||||
modified:
|
||||
- ../sm-seo-plugin/classes/resolver.go
|
||||
- ../sm-seo-plugin/classes/settings_write.go
|
||||
- ../sm-seo-plugin/classes/history.go
|
||||
- ../sm-seo-plugin/classes/crawl/fix.go
|
||||
- ../sm-seo-plugin/models/settings.go
|
||||
- ../sm-seo-plugin/models/settings/fields.yaml
|
||||
- ../sm-seo-plugin/models/revision.go
|
||||
- ../sm-seo-plugin/controllers/admin_registry.go
|
||||
- ../sm-seo-plugin/controllers/api/settings.go
|
||||
- ../sm-seo-plugin/extensions.go
|
||||
- ../sm-seo-plugin/admin_navigation.go
|
||||
- ../sm-seo-plugin/routes.go
|
||||
- ../sm-seo-plugin/mcp.go
|
||||
- ../sm-seo-plugin/operations.go
|
||||
- ../sm-seo-plugin/operations_test.go
|
||||
- ../sm-seo-plugin/seo_tab_test.go
|
||||
- ../sm-seo-plugin/assets/src/elements/index.ts
|
||||
- ../sm-seo-plugin/assets/src/elements/labels.ts
|
||||
- ../sm-seo-plugin/assets/src/elements/styles.css
|
||||
- ../sm-seo-plugin/assets/dist/seo.js
|
||||
- ../sm-seo-plugin/assets/dist/seo.css
|
||||
- ../sm-seo-plugin/lang/en/lang.yaml
|
||||
- ../sm-seo-plugin/lang/pl/lang.yaml
|
||||
- ../sm-seo-plugin/README.md
|
||||
- go.mod
|
||||
- go.sum
|
||||
- modules/lagoon/README.md
|
||||
- docs/database/attachments.md
|
||||
- .planning/research/STACK.md
|
||||
- ../sm-journal-plugin/controllers/api/posts.go
|
||||
- ../sm-journal-plugin/controllers/posts.go
|
||||
- ../sm-journal-plugin/plugin.go
|
||||
- ../sm-journal-plugin/config/config.yaml
|
||||
- ../sm-journal-plugin/go.mod
|
||||
- ../sm-journal-plugin/go.sum
|
||||
- ../sm-journal-plugin/README.md
|
||||
- ../sm-journal-plugin/related_media_test.go
|
||||
- ../vue-journal-layer/app/components/Journal/FeaturedImage.vue
|
||||
- ../vue-journal-layer/app/components/Journal/PostCard.vue
|
||||
- ../vue-journal-layer/app/journal-pages/PostPage.vue
|
||||
- ../vue-journal-layer/app/utils/types.ts
|
||||
- ../vue-journal-layer/app/assets/css/journal-base.css
|
||||
- ../vue-journal-layer/testing/fixtures/acme/posts.json
|
||||
- ../vue-journal-layer/testing/expect-acme.txt
|
||||
- ../vue-journal-layer/README.md
|
||||
|
||||
key-decisions:
|
||||
- "gen2brain/webp pinned exactly at v0.6.4 (Jakub 2026-11-04, recorded in 33-06); the decision note (1bb2ecf) is older than the go.mod change (5c07224)"
|
||||
- "The gen2brain/webp fallback is libwebp compiled to WASM and transpiled to Go by wasm2go (libwebp.go, about 2.7 MB of generated Go). There is no WASM runtime and no wazero; the first choice is the system libwebp through purego"
|
||||
- "Importing purego makes a binary dynamically linked against glibc even with CGO_ENABLED=0; -tags nodynamic keeps it static (transpiled encoder only). Recorded in the note, the lagoon README, docs/database/attachments.md and deferred-items for 33-14"
|
||||
- "An auto-link's locale is stored as '' for every locale (NOT NULL), not NULL as P33-45 wrote; the unique index is (lower(phrase), locale), and the save refuses a phrase of every locale that meets the phrase of one locale, both ways"
|
||||
- "Auto-link history uses subject autolink with purge slug * (a phrase can appear on any page); auto-links have no undo, like registry pages"
|
||||
- "autolinks and autolink_max are omitted on non-ok answers and from the sitemap filter's production resolve; with autolink_max 0 the answer carries no phrases"
|
||||
- "E10 is left out for the kinds collection and profile and for the page registry, because Words is a per-record fact (deferred item)"
|
||||
- "E10's inbound count is the distinct pages linking here, the outbound count the distinct site pages it links to; a page missing from the crawl gets 0 and 0 and is not called an orphan; suggestions skip pages already linked either way"
|
||||
- "WebP variants are written in mode exact at the original's aspect ratio for each width no larger than the original; every variant row carries the original's source_width and source_height, so the only new API key is variants"
|
||||
- "golem15_journal_image_variants has no foreign key to system_files (the plugin's migration tests run without framework tables); the API delete removes the rows"
|
||||
|
||||
patterns-established:
|
||||
- "Framework media add-ons: a separate sub-package (attach/webp) keeps a heavy dependency out of binaries that do not import it"
|
||||
|
||||
requirements-completed: [SC-2, SC-3, D-82, D-85, D-71, D-10]
|
||||
|
||||
coverage:
|
||||
- id: L1
|
||||
description: "Tracer: an all-locale phrase reaches another page's resolve answer with autolink_max 3, never the target page, never a disabled or other-locale phrase; autolink_max 0 answers none; 21 refused; three history rows"
|
||||
requirement: "D-82"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "../sm-seo-plugin/linking_test.go#TestAutolinksTracer"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: L2
|
||||
description: "Auto-links screen and API: the refusals (short phrase, phrase taken naming its URL, javascript:, data:, protocol-relative, ftp, whitespace), labels, the disabled row state, the active and locale filters, Add focus keyphrases twice (second adds nothing), MCP origin, update, delete, history vias"
|
||||
requirement: "D-82, D-10, T-33-50"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "../sm-seo-plugin/linking_test.go#TestAutolinks"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: L3
|
||||
description: "E10 data: null counts before a crawl; after it, inbound/outbound/orphan and both suggestion lists with public and admin addresses; the widget action answers the same; seo_links is in the SEO tab after seo_graph"
|
||||
requirement: "D-82"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "../sm-seo-plugin/linking_test.go#TestLinkSuggestions"
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: "../sm-seo-plugin/seo_tab_test.go#TestSEOTab"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: L4
|
||||
description: "E10 element: counters, orphan callout, both lists, notCrawled, none, checking line with aria-busy, danger line on failure, Copy address and the copied status for 2 s, hidden button without a clipboard, locale switch"
|
||||
requirement: "D-82"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "../sm-seo-plugin/tests/elements/links.test.ts"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: L5
|
||||
description: "MCP parity: six new tools, every Auto-links and E10 operation mapped, reads are GET, the delete is destructive"
|
||||
requirement: "D-10"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "../sm-seo-plugin/operations_test.go#TestEveryAdminOperationHasTool"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "../sm-seo-plugin/operations_test.go#TestMCPReadToolsAreGET"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: M1
|
||||
description: "WebP encoder: RIFF/WEBP output, VariantKey rules (webp original appends), a variant next to its thumbnail written once, a failed encode leaves no file, the documented example"
|
||||
requirement: "D-85"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "modules/lagoon/attach/webp/webp_smoke_test.go"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: M2
|
||||
description: "Journal: the upload answers before any encoding and queues the job; the job writes 480 and 800 (1200 wider than the 1000 px original) with WebP copies; the API's variants; a second run writes nothing; an admin save queues only for an image lacking a width; the API delete removes rows and blobs"
|
||||
requirement: "D-85"
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: "../sm-journal-plugin/related_media_test.go#TestImageVariants"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: M3
|
||||
description: "Layer: srcset strings from variants plus the original's width; <picture> with image/webp, width, height, alt, eager with fetchpriority high on the post, lazy in cards; an image without variants renders as before"
|
||||
requirement: "D-85, D-71"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "../vue-journal-layer/tests/srcset.test.ts"
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: "../vue-journal-layer/testing/expect-acme.txt (featured image lines, smoke-ssr 87 checks)"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
|
||||
duration: 75min
|
||||
completed: 2026-11-04
|
||||
---
|
||||
|
||||
# Phase 33 Plan 11: auto-links, internal link suggestions and WebP featured images Summary
|
||||
|
||||
**Editors keep a phrase → URL table that every ok resolve answer carries for its locale (minus the page itself), with a screen, a keyphrase feed and MCP tools; the SEO tab shows each page's inbound and outbound links from the last crawl with linking suggestions (E10); featured images get thumbnails at 480/800/1200 and a WebP copy of each from a Journal conga job, encoded in Go by the framework's new `attach/webp` package (github.com/gen2brain/webp v0.6.4), and the journal layer serves them as `<picture>` with srcset.**
|
||||
|
||||
## Read first: consumers, versions, what is not done
|
||||
|
||||
- **Everything is additive.** No exported identifier, route, tool, lang key, table or answer key was removed or renamed in any repository.
|
||||
- sm-seo-plugin: the resolve answer gains `autolinks` and `autolink_max` on ok answers; the settings answer gains `autolink_max`. New routes `GET/POST /_seo/api/v1/autolinks`, `PUT /autolinks/{id}`, `DELETE /autolinks?ids=`, `POST /autolinks/from-keyphrases`, `GET /links?url=`. Six MCP tools. Migration 202610280007 (table and settings column).
|
||||
- Framework: `attach.VariantKey` and the new package `modules/lagoon/attach/webp`. The gate's additive check passes (5 packages against v0.6.0, `modules/lagoon/attach` +1 line).
|
||||
- sm-journal-plugin: `featured_images[].variants` (new key on every entry, `[]` until made), migration 202610280002, job `golem15.journal.image_variants`, command `journal:image-variants`, config key `golem15.journal.image_widths`. The plugin now registers jobs (`pact.HasJobs`).
|
||||
- vue-journal-layer: `JournalFeaturedImage` gains the optional props `image` and `sizes`. Without variants its markup is byte-for-byte as before.
|
||||
- **New dependency: `github.com/gen2brain/webp` v0.6.4, pinned exactly**, in the framework go.mod (and indirect in sm-journal-plugin's). The only package that imports it is `modules/lagoon/attach/webp`. Its one dependency, `github.com/ebitengine/purego` v0.10.1, was already in the module graph, so no other module was added. The module source confirms the fallback is wasm2go-transpiled Go, not wazero.
|
||||
- **Linking consequence, which 33-14 should decide on.** A binary that imports `attach/webp` (any app mounting sm-journal-plugin) is dynamically linked against glibc even with `CGO_ENABLED=0`, because of purego. It needs `ld-linux-x86-64.so.2`, `libc.so.6`, `libdl.so.2` and `libpthread.so.0`. It runs on the Debian servers but not in scratch or musl images. `-tags nodynamic` gives a static binary with the slower transpiled encoder. Installing `libwebp-dev` enables the fast path. The note, the lagoon README, the attachments docs and deferred-items all say this.
|
||||
- **Not pushed or tagged.** sm-seo-plugin is 26 commits ahead of v0.1.1. sm-journal-plugin, vue-journal-layer and summercms.go have unpushed phase-33 commits. The apps still pin the old plugin submodules, and 33-14 bumps them.
|
||||
- **Settings tile and permission texts** (33-10 deferred item) now carry the UI-SPEC texts in en and pl, and the deferred item is marked resolved.
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** about 75 min (2026-11-04, about 15:05Z to 16:20Z)
|
||||
- **Tasks:** 4 (a tracer, two TDD tasks, one auto task)
|
||||
- **Commits:** 7 across 4 repositories (sm-seo-plugin 3, summercms.go 2, sm-journal-plugin 1, vue-journal-layer 1); measured with `git rev-list --count <before>..HEAD` per repository
|
||||
|
||||
## Task Commits
|
||||
|
||||
**sm-seo-plugin** (`/media/nvme/dev/golem15/summercms.io/summercms/sm-seo-plugin`, main, b553fc2..49cc241)
|
||||
1. Task 1 (tracer): `6203aec` feat: auto-link phrases in the resolve answer (tracer)
|
||||
2. Task 2A: `ab4e834` feat: auto-links screen, keyphrase feed and MCP tools
|
||||
3. Task 2B: `49cc241` feat: internal links widget with counts and suggestions
|
||||
|
||||
**summercms.go** (master, 3bfdc86..5c07224)
|
||||
4. Task 3A: `1bb2ecf` docs(33-11): WebP decision note and STACK row
|
||||
5. Task 3B: `5c07224` feat(lagoon): WebP variants of attachment thumbnails
|
||||
|
||||
**sm-journal-plugin** (main, ee3cb26..4f5bd19)
|
||||
6. Task 4A: `4f5bd19` feat: image variants job and variants in the post API
|
||||
|
||||
**vue-journal-layer** (main, adcea4d..1080a3f)
|
||||
7. Task 4B: `1080a3f` feat: WebP and srcset for featured images
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- **Auto-links (D-82).** The table has a phrase, a URL (a `/` path or an http(s) address), a locale or all, an active switch, an origin (manual, keyphrase or mcp) and a note. Every write is checked and recorded in history, which purges the site cache with slug `*`.
|
||||
- `Service.ForPage` lists the active phrases of a locale, longest first. It drops phrases that link to the page itself, as a path or as an absolute URL on the site host or its www twin.
|
||||
- The resolver adds `autolinks` and `autolink_max`, and vue-seo-layer's `autolink()` (33-09) applies them.
|
||||
- **Auto-links screen (C22).** It sits after Pages with the icon `wand-sparkles`. The list shows 50 rows, has checkboxes and setup, and its toolbar holds create, delete and Add focus keyphrases.
|
||||
- Columns: phrase, links to, language (All languages or the code), origin label, updated. A row gets the disabled state ("Off") when the phrase is inactive.
|
||||
- Filters: language, origin, and the Active only switch.
|
||||
- Form: phrase and URL, language (empty option All languages) and Active, note.
|
||||
- Refusals: `phrase_short`, `phrase_taken` (names the URL), `url_invalid`.
|
||||
- Add focus keyphrases turns the SEO entries' focus keyphrases into phrases (one per locale, origin keyphrase) and toasts the count (CLDR plural) or `keyphrases_none`.
|
||||
- **E10 golem15-seo-links (C19).** The `seo_links` widget sits in the SEO tab after `seo_graph`, on update only.
|
||||
- It shows the counters (`.summer-stats`) with the crawl time, the orphan callout, "Pages you could link to from this text" with Copy address, and "Pages that could link here" with admin links.
|
||||
- Empty states: `notCrawled` before the first crawl, `none` under an empty list.
|
||||
- Loading and errors: the checking line with `aria-busy`, a danger line on failure. Copy address falls back to the selectable URL when the clipboard is unavailable, and the element follows the ML locale switch.
|
||||
- `classes/linkgraph.go` reads the last finished crawl's pages and links. The crawler's orphan suggestions now share `classes.NormalText`.
|
||||
- **Framework WebP (P33-42).**
|
||||
- `attach.VariantKey(thumbKey, format)` replaces the thumbnail's extension, or appends the format when the thumbnail already has that extension.
|
||||
- `webp.Encode` encodes at quality 80.
|
||||
- `webp.Variant` calls ThumbKey first and returns early when the variant exists. It encodes in memory, stores `image/webp`, and deletes the key on a write or close error.
|
||||
- Docs section "WebP variants" in docs/database/attachments.md, with a synced `src=` example.
|
||||
- **Journal image variants.**
|
||||
- The job runs with three attempts. It makes each configured width no larger than the original (mode exact, the original's aspect ratio) through ThumbKey and `webp.Variant`, then inserts rows with ON CONFLICT DO NOTHING. It skips originals that are missing, don't decode or are over the pixel limit.
|
||||
- It is queued after an API upload and after an admin save of a post whose images lack a width. The upload never encodes, and a queue failure is only logged.
|
||||
- `journal:image-variants [--all]` backfills.
|
||||
- `featured_images[].variants` is loaded with one query per post.
|
||||
- The API delete removes a file's rows.
|
||||
- **Layer.**
|
||||
- `imageSrcsets` builds the WebP and original-format srcsets, adds the original when it is wider than every variant, works out the reserved size, and escapes addresses.
|
||||
- `JournalFeaturedImage` renders `<picture>`: cards pass `sizes="(min-width: 48rem) 33vw, 100vw"` and load lazily; the post page passes `sizes="(min-width: 46rem) 46rem, 100vw"` with eager loading and `fetchpriority="high"`.
|
||||
- `picture` is styled as a block element.
|
||||
|
||||
## Decisions Made
|
||||
|
||||
See `key-decisions` above.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 3 - Blocking] Auto-link locale stored as '' instead of NULL**
|
||||
- **Found during:** Task 1
|
||||
- **Issue:** P33-45 has `locale null = all`. The admin dropdown's empty option posts "", and a nullable column would need a custom binding type.
|
||||
- **Fix:** `locale TEXT NOT NULL DEFAULT ''`, unique `(lower(phrase), locale)`. The save check treats a phrase of every locale as meeting each locale, both ways. "all" is accepted as an alias in the API and the filter.
|
||||
- **Commit:** `6203aec`
|
||||
|
||||
**2. [Rule 2 - Missing] Auto-link history purges every page**
|
||||
- **Issue:** `purgeSlug` would have used the autolink's target `url` as the purge slug, but a phrase changes the pages that contain it, not its target.
|
||||
- **Fix:** `SubjectAutolink` purges `*`.
|
||||
- **Commit:** `6203aec`
|
||||
|
||||
**3. [Rule 3 - Blocking] E10 omitted by kind, not by Words**
|
||||
- **Issue:** `Words` is per record and locale (`pact.ContentLocale`), so the per-type SEO tab cannot read it.
|
||||
- **Fix:** `hasBody(key, kind)` leaves E10 out for collection, profile and the page registry. Logged in deferred-items with a framework fix (`ContentDescriptor.HasBody`).
|
||||
- **Commit:** `49cc241`
|
||||
|
||||
**4. [Rule 2 - Missing] E10 checking and failure copy**
|
||||
- **Issue:** The E10 copy table has no loading or failure text, and E3's `checking` says "Checking for duplicates…".
|
||||
- **Fix:** Added `linksChecking` ("Loading the internal links…" / "Wczytywanie linków wewnętrznych…") and `linksFailed` ("The internal links could not be loaded. Try again." / "Nie udało się wczytać linków wewnętrznych. Spróbuj ponownie."). E10 keys are prefixed `links*` because `none` and `links` already exist in the shared dictionary.
|
||||
- **Commit:** `49cc241`
|
||||
|
||||
**5. [Rule 2 - Missing] The original's size in the variants**
|
||||
- **Issue:** The layer needs the original's width for the srcset and the reserved size, and `system_files` has no dimensions.
|
||||
- **Fix:** Each row and each `variants` entry carries `source_width` and `source_height`, so `variants` stays the only new API key.
|
||||
- **Commit:** `4f5bd19`
|
||||
|
||||
**6. [Rule 3 - Blocking] No foreign key from the variants table to system_files**
|
||||
- **Issue:** The plugin's own migration tests (updates, models) run without the framework's tables, and the foreign key broke five tests.
|
||||
- **Fix:** Plain `file_id`. The API delete removes the rows; leftovers from admin removals are logged in deferred-items.
|
||||
- **Commit:** `4f5bd19`
|
||||
|
||||
**7. [Rule 1 - Bug] The admin save re-queued the job for complete images**
|
||||
- **Issue:** The "missing" filter counted variant rows against the number of widths, but widths wider than the original never get a row.
|
||||
- **Fix:** An image counts as missing only when it lacks a width no larger than its recorded source width.
|
||||
- **Commit:** `4f5bd19`
|
||||
|
||||
**8. [Rule 3 - Blocking] Docs code blocks need a `src=` snippet**
|
||||
- **Issue:** `docs:build --check` refuses a Go block without `src=`.
|
||||
- **Fix:** Added `ExampleVariant` to the webp tests (external test package, plus `export_test.go` for the encoder seam), referenced with `src=` and synced.
|
||||
- **Commit:** `5c07224`
|
||||
|
||||
### Plan assumptions adapted
|
||||
|
||||
- **Files that did not apply.** `modules/lagoon/attach/README.md` does not exist, so attach is documented in `modules/lagoon/README.md`. `models/registry.go` and `updates/registry.go` (both plugins) needed no change, because new files register themselves in `init`. `classes/settings_write.go` also carries the `autolink_max` range through `Settings.AuditProblems` (0 to 20, `lagoon::validation.between.numeric`).
|
||||
- **The plan's description of the fallback.** It says "WASM via wazero". The v0.6.4 source is wasm2go-transpiled Go, and the decision note says so.
|
||||
- **`TestJournalAPIMatrix` does not exist.** The plan's verify names it, but the matrix file holds several tests (`TestJournalAPIShowNeighbors`, `TestJournalPerPageClamp`, …). The full Journal suite (`go test ./...`, Docker) ran green instead.
|
||||
- **Acceptance grep `golem15.seo.autolinks` in admin_navigation.go.** The navigation uses the constant `controllers.AutolinksControllerID`, so a comment on the nav entry names the controller ID.
|
||||
- **TestAutolinks reads list rows by phrase, not by position.** cabana sorts the phrase column in the database's byte order. Logged as a deferred item.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
- `gofmt` turned `''` in a Go comment into a typographic quote. The comment now reads "empty".
|
||||
- The SSR expectation grammar splits on ` type=`, so the `<source type="image/webp"` lines start their `contains=` after `type`.
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None.
|
||||
|
||||
## Threat Flags
|
||||
|
||||
None beyond the plan's threat model.
|
||||
- T-33-50: URLs are restricted to paths and http(s), tested with javascript:, data:, protocol-relative and ftp addresses.
|
||||
- T-33-51: the pixel limit is checked from the header before any decode, widths wider than the original are skipped, and encoding runs in a job with attempts.
|
||||
- T-33-52: the version is pinned, and the note and STACK row were committed before go.mod.
|
||||
- T-33-53: every new route sits behind `manage()` with `golem15.seo.manage`, and the parity test covers the tools.
|
||||
|
||||
## Verification run
|
||||
|
||||
- **sm-seo-plugin:** `go vet ./...`, `go test -short ./...` and the full Docker suite (`go test ./...`, about 100 s) were green before each of the three commits.
|
||||
- `TestAutolinksTracer`, `TestAutolinks`, `TestLinkSuggestions`, `TestEveryAdminOperationHasTool`, `TestMCPReadToolsAreGET` and `TestSEOTab` all PASS.
|
||||
- `npm run test`: 93/93 (9 files). `npm run build` then `scripts/check-dist.sh`: "assets/dist matches a fresh build".
|
||||
- Acceptance greps: two resolver json tags; `autolink_max` in settings_write.go and fields.yaml; six tool names in mcp.go; `golem15-seo-links` in index.ts; `golem15.seo.autolinks` in admin_navigation.go.
|
||||
- **summercms.go:**
|
||||
- `go vet ./...` and `go test -short ./...` green. `go test ./modules/lagoon/...` (Docker): lagoon, attach and attach/webp all ok.
|
||||
- `summer docs:build --check`: no problems. `go test ./cmd/summer -run TestDocsTree`: ok.
|
||||
- `grep -rln gen2brain modules --include=*.go` lists only `modules/lagoon/attach/webp/webp.go`.
|
||||
- Note before go.mod: `1bb2ecf` is the parent of `5c07224`, the first gen2brain change to go.mod.
|
||||
- **sm-journal-plugin:** `go vet ./...`, `go test -short ./...` and the full Docker suite all green. `TestImageVariants` PASS. `GOWORK=off go build ./... && go vet ./...` also green (go.mod has the framework replace). `TestCommandsDocumented` logs nothing for `journal:image-variants`.
|
||||
- **vue-journal-layer:**
|
||||
- `npm run test`: 192 pass, 0 fail (srcset.test.ts: 5).
|
||||
- `npm run build:playground`: complete. `npm run smoke:ssr`: ok (87, 2, 2, 2, 2 checks).
|
||||
- TypeScript: the package has no typecheck script. `tsc --noEmit -p .playground/.nuxt/tsconfig.app.json --allowImportingTsExtensions` reports 3 pre-existing errors in untouched files (useJournalItemList.ts, siteCache.ts) and none in the changed files. .vue files are not covered by tsc.
|
||||
- **`scripts/check-phase33.sh`:** self-test, go, plugins, docs, marker, additive (5 packages, attach +1 line), admin, docker, consumers and compat all PASS. compat printed the known refusals for sm-feedback-plugin, sm-presentations-plugin and sm-quizzes-plugin (skipped, they fail at v0.6.0 too). `--layers` was skipped (known broken, deferred-items 33-08), and its steps were run by hand for vue-journal-layer as listed above.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None for the defaults. Optional: install `libwebp-dev` on servers for about five times faster encoding. For a static or musl deploy of an app with Journal, build with `-tags nodynamic`. Run `journal:image-variants` once per site to backfill existing featured images.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- **33-12 / 33-13:** E10, Auto-links and the resolve keys are in place. vue-seo-layer already consumes `autolinks` and `autolink_max` (33-09).
|
||||
- **33-14:**
|
||||
- Bump the sm-seo-plugin, sm-journal-plugin and vue-journal-layer pins in the apps. sm-journal-plugin then needs the framework at v0.6.1 (`attach/webp`).
|
||||
- Decide each app's build on the glibc linking (`-tags nodynamic` or not).
|
||||
- Name the new migrations (SEO 202610280007, Journal 202610280002), the `journal:image-variants` backfill and the gen2brain/webp dependency in the release notes.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- Files: every created file listed in `key-files.created` exists (checked with `test -f` in each repository).
|
||||
- Commits: `6203aec`, `ab4e834`, `49cc241` (sm-seo-plugin); `1bb2ecf`, `5c07224` (summercms.go); `4f5bd19` (sm-journal-plugin); `1080a3f` (vue-journal-layer). Each was found with `git cat-file -e`.
|
||||
@@ -71,6 +71,29 @@ Out-of-scope discoveries logged during execution; not fixed in the plan that fou
|
||||
- **Possible fix:** let partials carry an absolute http(s) link that the SPA opens in a new tab with `rel="noopener noreferrer"` (the account contributions already accept such URLs), then turn the two lines into links.
|
||||
- **Why deferred:** a change to cabana's sanitizer and the SPA, outside 33-10's repository.
|
||||
- **The settings tile description and the permission comment still have the Phase 32 text (sm-seo-plugin).** UI-SPEC's replacement texts name auto-links, which arrive in 33-11, together with the site audit.
|
||||
status: open
|
||||
status: resolved (33-11, sm-seo-plugin `ab4e834`: `settings.description` and `permissions.manage_comment` in en and pl carry the UI-SPEC texts)
|
||||
- **Possible fix:** 33-11 replaces `settings.description` and `permissions.manage_comment` in both phrasebooks with the UI-SPEC texts once Auto-links exists.
|
||||
- **Why deferred:** the new texts describe a screen that does not exist yet.
|
||||
|
||||
## From 33-11 (sm-seo-plugin, framework, sm-journal-plugin)
|
||||
|
||||
- **Binaries that mount sm-journal-plugin are dynamically linked against glibc (framework `attach/webp`, decision needed in 33-14).** `github.com/gen2brain/webp` loads the system libwebp through purego, and purego's no-cgo runtime makes an importing binary dynamically linked even with `CGO_ENABLED=0`: it needs `/lib64/ld-linux-x86-64.so.2`, `libc.so.6`, `libdl.so.2` and `libpthread.so.0`. It runs on the Debian servers. It does not run in a `scratch`, distroless-static or musl (Alpine) image. `-tags nodynamic` keeps the binary static and uses only the transpiled encoder (about five times slower). Recorded in `.planning/notes/2026-10-28-webp-gen2brain.md`.
|
||||
status: open
|
||||
- **Possible fix:** none needed for the current deploys. For a static deploy, add `-tags nodynamic` to the app's `scripts/build.sh` (or `build.tags` in `summer.yaml`). For the fast path, install `libwebp-dev` on the server: the package opens the unversioned `libwebp.so` and `libwebpdemux.so`.
|
||||
- **Why deferred:** a deployment choice per application; 33-14 bumps the apps and decides.
|
||||
- **E10 is left out by content kind, not by the descriptor's `Words` (sm-seo-plugin).** UI-SPEC C16 row 14 omits `seo_links` for types whose descriptor supplies no body (`Words` null). `Words` is a per-record, per-locale fact (`pact.ContentLocale.Words`), so the SEO tab, compiled per type at boot, cannot read it. 33-11 omits E10 for the kinds `collection` and `profile` and for the page registry (`seo-page`), and shows it for every other type.
|
||||
status: open
|
||||
- **Possible fix:** a type-level fact such as `pact.ContentDescriptor.HasBody` (framework, additive), read by `hasBody` in `sm-seo-plugin/extensions.go`.
|
||||
- **Why deferred:** a framework change, outside 33-11's repositories for this task.
|
||||
- **The Auto-links list sorts by phrase in the database's byte order (framework).** cabana's sortable column orders by the raw column, so with the test database's collation "Lwia grzywa" comes before "boczniak". `GET /_seo/api/v1/autolinks` sorts by `lower(phrase)`.
|
||||
status: open
|
||||
- **Possible fix:** a case-insensitive or collated sort option on list columns in cabana (`lagoon.Collate` exists for queries), then use it on `phrase` in `sm-seo-plugin/models/autolink/columns.yaml`.
|
||||
- **Why deferred:** generic, additive framework work.
|
||||
- **Variant rows of a featured image removed in the admin stay in `golem15_journal_image_variants` (sm-journal-plugin).** The table has no foreign key to `system_files`, because the plugin's migration tests run without the framework tables. `DELETE /_journal/api/v1/posts/{id}/featured-images/{fileId}` deletes the rows. A file removed through the admin's file field leaves them; they are never listed, because the API joins on the post's current files, and their blobs go with the thumbnail prefix.
|
||||
status: open
|
||||
- **Possible fix:** delete rows whose `file_id` no longer exists in the posts controller's save hook, or in `journal:image-variants`.
|
||||
- **Why deferred:** harmless leftovers; a cleanup belongs with the next Journal media change.
|
||||
- **A post whose featured image is narrower than every configured width queues a no-op job on each admin save (sm-journal-plugin).** Such an image never gets a variant row, so `DispatchPostImageVariantsAfterCommit` counts it as missing. The job reads its header and writes nothing.
|
||||
status: open
|
||||
- **Possible fix:** record the original's size for files without variants (for example a row with width 0), and skip them in `featuredFiles`.
|
||||
- **Why deferred:** cheap and rare (featured images are usually wider than 480 px).
|
||||
|
||||
Reference in New Issue
Block a user