Commit Graph
100 Commits
Author SHA1 Message Date
m038andClaude Opus 5 bb22c368ba chore: bump user/ pin to 1b9e51b (story fixes, blueprints, travelling off)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 23:16:27 +02:00
m038andClaude Opus 5 143ec135c0 test(stories): cover scrolly step text and back-to-top; fix build-output note
S8 asserts scrolly-section splits its slot content into visible step panels
(the blank-text-column regression had no coverage: S3 only checked the image
column existed, and the silent early return threw nothing for S4 to catch).
S9 covers back-to-top now that main.js solely owns it, including the history
entry the removed inline copy lacked.

CLAUDE.md attributed css-compiled/ and fonts/ to css/style.css and
css/tokens.css. They are built from the js/src/ entrypoints' CSS and font
imports; css/ is hand-authored and served directly, needing no rebuild.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 23:16:22 +02:00
m038andClaude Opus 5 e106da0206 docs(guides): add story-authoring guide; list it in the README index
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 23:16:22 +02:00
m038andClaude Opus 5 cfe070efec fix(make): worktree-rm no longer unregisters user/ for the main checkout
`submodule deinit` is needed before `worktree remove` (a populated user/
blocks it), but worktrees share .git/config — so deiniting inside the
worktree stripped submodule.user.url globally. After any `make worktree-rm`
the main checkout's `git submodule status` reported `-` (not initialised)
while user/ sat there fully intact, and a later `submodule update` would
have had no URL to work from. Re-register with an idempotent `submodule
init` after the removal.

Found by tearing down the post-location-override worktree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 23:54:43 +02:00
m038andClaude Opus 5 3250ad366a docs(working): close the plan; retract the owner_username diagnosis
Merged state recorded (user/ dd19995, outer 4450bd6, pin bumped).

The "owner_username cluster" was wrong: on merged main only DEL4 fails, with
identical site.yaml and content, so auth was never the cause. The worktree's
extra five failures came from its incomplete git-ignored user/plugins/ set.

DEL4 itself is real and stays open — deleting an entry removes it from the
DOM and from disk, but a fresh trip-page load makes the server re-emit the
card, the same invalidation bug the spec's header says was fixed once before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 23:53:24 +02:00
m038andClaude Opus 5 4450bd6eec Merge feat/post-location-override into main
Post-form location override (U1-U6) plus the code-review hardening, the
maplibre-CSS lazy <link>, and the test-entry leak fix. Bumps the user pin to
dd19995, the corresponding user/ merge-to-main commit.

CLAUDE.md conflicted because both sides changed it deliberately: main cut it
to rules-only (839a4d0, ed6e43a) while this branch added the map-doctrine
carve-out (829325c). Resolved to main's rules-only structure with the
carve-out ported into it — without it CLAUDE.md would forbid the second map
engine this feature deliberately ships. The descriptive detail stays in
docs/reference/architecture.md, per main's content-tiering convention.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 23:49:36 +02:00
m038andClaude Opus 5 28bbd41868 docs(working): record the submodule-git-dir cause and the .env fix
~/Projects is a symlink to ~/Nextcloud/Projects — one directory, not two
clones. The differing user/main refs came from the worktree having its own
submodule git dir (.git/worktrees/<name>/modules/user), which is worth
knowing: submodule commits made from the main checkout stay invisible in a
worktree until fetched, and a local fetch moves them without a push.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 23:47:37 +02:00
m038andClaude Opus 5 d57041d316 test(post): retract the "these specs are red" notes — the merge fixed them
The warnings added in 6398542 were wrong. UG1, UG2 and LD1 were failing
because this branch predated e17a5dc, not because the behaviour they assert
was missing: merging user/main brought the FilePond upload gate and the
oriented-derivative slide dims, and all three pass with no product change.

Headers now point at e17a5dc for both mechanisms. Also corrects the plan's
.env note — the env layering is intentional (.env global, .env.<ENV> per
environment via the generated remote-*-<env> targets); the actual fault is
just that `-include .env` additionally requires makefile-valid syntax and
line 6 is not, which breaks make in both non-worktree clones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 23:40:48 +02:00
m038andClaude Opus 5 b02f27f559 docs(working): record three real defects behind the red post specs
Also corrects the green-run line: "test-post 6/6" is the scripts/test-post.sh
shell suite, not the Playwright specs under tests/ui/post/ — conflating the
two made the Playwright post specs look covered when they were never run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 23:28:49 +02:00
m038andClaude Opus 5 6398542845 test(post): resolve USER_DIR via helpers; record why UG1/UG2/LD1 are red
lightbox-dims.spec.js hardcoded ../../../user, so a run against a checkout
detached from the served tree planted its fixture in a different user/ than
Grav renders and LD1 failed as an opaque "card never appeared" timeout.
Take USER_DIR from helpers instead, which honours GRAV_USER_DIR.

The three specs in this folder that fail do so for real, pre-existing
reasons, and both files' headers implied otherwise:

- UG1/UG2 specify a submit gate that is not implemented. post-form.js's
  only create-form guard is `converting > 0` (pre-FilePond HEIC
  conversion); it never inspects FilePond item state at submit time, and
  .photo-convert-status is created lazily by photoStatusEl() only from the
  HEIC paths — so for a plain JPEG the element never exists and both
  expectations fail as "element(s) not found". UG2 is the one that matters:
  a failed upload keeping its thumbnail is unguarded silent data loss.

- LD1's header described its root cause in the past tense, reading as
  fixed. entry-journal.html.twig:48-49 still emits {{ img.width }} /
  {{ img.height }}, so EXIF-rotated photos still declare pre-rotation dims
  and PhotoSwipe still squeezes them.

Left failing rather than skipped, per retries:0 — a red test here is a real
defect, and hiding these would lose both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 23:28:18 +02:00
m038andClaude Opus 5 e79275a3ab build: give Grav's form-upload staging its own named volume
The image declares /var/www/html as a VOLUME, so tmp/forms/<session>/ —
where Grav parks FilePond uploads until the submit moves them into the page
folder — lived in an anonymous volume that any container-recreating
`docker compose up` discards, dropping the photos of a post that was filled
in but not yet submitted. grav_tmp gives the staging area its own lifecycle.

Also pins makefile.configureOnOpen off in the workspace, so the VS Code
Makefile extension stops trying to configure a Makefile whose `-include
.env` cannot be parsed as makefile syntax.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 23:27:18 +02:00
m038 f9ab3b1561 docs(working): record the green run, the lazy-link, and what's left before merge 2026-07-24 22:34:20 +02:00
m038 1f4e2aeba5 fix(test): close the test-entry leak into real trip content
A ui-test entry had survived into the active trip's dailies. Three independent
failures had to line up for that, and all three were real:

1. cleanupEntry() used host-side fs.rmSync. Grav's Apache workers run as root,
   so every entry the form creates is root-owned and recursive removal needs
   write permission on that directory — which the host user lacks. Cleanup had
   never worked for form-created entries; it just threw inside a path nothing
   checked. It now falls back to `docker exec … rm -rf` in the container that
   actually serves USER_DIR.

2. globalTeardown's dailies sweep keyed off a `parent:` in post-form.md — a key
   deliberately removed (the write target comes from site.yaml active_trip, and
   CLAUDE.md forbids re-adding a static parent). The regex could never match, so
   dailiesDir was always null and the sweep silently did nothing. It now reuses
   helpers' own resolution instead of keeping a divergent copy.

3. Nothing pinned the suite to this checkout's server. playwright.config.js
   defaults to :8081, so a worktree run hit the MAIN checkout — entries created
   in one content tree while the specs asserted and cleaned up in another.
   test-ui now passes GRAV_BASE_URL from GRAV_PORT, and globalSetup hard-fails
   when the server's bind mount disagrees with the tree the specs read.

Also fixed, found on the way to a green run:

- test-account interpolated the password into an `sh -c` string, so a password
  containing a shell metacharacter was re-parsed by the container's shell
  (`sh: 2: <fragment>: not found`, no account, every UI run dead). It now
  travels via `docker exec -e`, making the recipe indifferent to its contents.
- `make start` in a worktree always failed: travel-memories declares
  `env_file: .env` and worktree-new creates none. It degrades to start-grav
  there — a worktree with no server is what sent runs to :8081 in the first
  place.
- test-form-config asserted a hero_image field that 8cf1145 deliberately
  removed; it had been failing ever since.

Verified: config 22/22, post 6/6, location-override 20/20, and a full UI run
now leaves zero ui-test entries behind. The remaining UI failures are
pre-existing on main — site.yaml pins owner_username to a real account while
the suite logs in as testrunner, so owner-only controls never render for it.
Only trip-publish.spec.js patches that; delete-flow, edit-mode and anon-view
do not. Left for a separate branch.
2026-07-24 22:33:46 +02:00
m038andClaude Opus 5 cdae34a706 docs(solutions): capture the CLAUDE.md content-tiering convention
Four rounds of CLAUDE.md reduction (255 -> 305 -> 179 -> 74 lines) turned
up one consistent finding: every stale fact was a *description* of code or
config, never a rule. Two had been written by Claude days earlier.

Documents the operational test ("does this line change what Claude does on
a task where it wouldn't otherwise open the relevant file?"), the tiering
table, why gotchas are the one category that cannot move to a read-on-demand
docs/exceptions/, invariants-over-enumerations, and how to tell when a
reduction pass has hit the floor.

Also surfaces the docs/solutions frontmatter fields in CLAUDE.md's
entry-point table so the store is greppable by module, not just browsable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 22:26:25 +02:00
m038andClaude Opus 5 285e61573e docs: state the build-output rule as an invariant, not a file list
Enumerating the bundles meant adding a fifth one silently falsified
CLAUDE.md. "Everything in js/ is generated except js/src/,
maplibre-utils.js and nav.js" is exactly true today and stays true.
The source->output table lives in docs/reference/architecture.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 21:53:20 +02:00
m038andClaude Opus 5 5edaf3ee1e docs(review): correct R8/R13 and the plan status; bump user pin to the review fixes
R8 and R13 both described behaviour that changed in review, and R13 rested on a
server-side cleanCoordinate() that had never been committed. Both now describe
what actually ships, with the revision called out inline rather than silently
rewritten. The plan's Status keeps  Complete but now records what the review
changed and the two things still open before merge.

Bumps the `user` gitlink to e873a9c (the review fixes). The submodule is
deliberately NOT pushed: git-sync would propagate it to production. So this pin
still references a commit that exists only locally — push `user/` and re-point
before this branch merges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 21:49:02 +02:00
m038andClaude Opus 5 9ec2349cd6 docs(working): add a human-facing index + plan status reference
CLAUDE.md keeps the status convention as a one-line rule (it has to be
loaded to be followed). This is the same convention written out for a
human reader, with the meanings the trim dropped, plus what each
subfolder of docs/working/ is for and a grep one-liner for "what's open".

Notes the two distinctions that matter in practice: Deferred is not
Abandoned, and a trailing note after "Complete" is normal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 21:48:00 +02:00
m038andClaude Opus 5 829325c9c7 fix(review): make the mismatch-blocks-submit test able to fail; carve out the map doctrine
The U5 guard asserted only `.notices` toHaveCount(0) and toHaveURL(/\/post/).
Both pass instantly, and both also hold for a *successful* submit — the form
posts to /post and only renders its notice after the round trip, and the click
is dispatched via evaluate(el => el.click()), which skips Playwright's
navigation-aware waiting. So the one test standing between a bad coordinate
and the server could not fail. It now proves the negative on disk via
findEntry() after a settling interval, registers the tag for cleanup before
the click, and asserts the flag and value survived.

CLAUDE.md's "one map path" section stated flatly that a single map code path
exists, which location-map.js now contradicts. Recorded it as the one
sanctioned exception (an editor, not a display map; lazy-imported; shares only
MAP_STYLE) rather than leaving the doctrine wrong.

The `user` gitlink is deliberately NOT bumped here: its pin already points at
an unpushed submodule commit, which must be pushed and re-pointed before this
branch merges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 21:34:17 +02:00
m038andClaude Opus 5 839a4d0e69 docs: cut CLAUDE.md to rules-only (179 → 74 lines)
CLAUDE.md now carries only what must be known *before* opening a file:
hard rules, gotchas, and an entry-point table. Everything descriptive
moved to the doc that lives next to the code.

Moved out:
- stack versions, plugin roles, asset pipeline, nav shape, user/ repo
  tracking rules → docs/reference/architecture.md
- Playwright layout, config facts, auth-setup project, test account
  → docs/reference/testing.md (new)
- folder map, full make command list (build/test/demo/worktree targets
  that only existed in CLAUDE.md) → README.md
- dev/prod Twig settings table → already in docs/guides/deploy-cycle.md

Fixed while verifying, all of them descriptions that had drifted:
- demo fixtures were listed as italy-2026-demo + no-photos-demo; the
  actual folders are italy-2025 + italy-2026-demo
- the map engine was cited at js/src/maplibre-utils.js; it is
  js/maplibre-utils.js, a hand-authored file beside the bundles
- the build-output list omitted fonts/ and the generated
  templates/partials/weather-icons.html.twig, and did not flag that
  js/maplibre-utils.js and js/nav.js are sources living in js/
- README called user/ a "standalone git repo" (it is a submodule)
- docs/README.md linked to a non-existent working/production-todo.md
- git-sync-notes.md pointed at "CLAUDE.md §1", a section number that
  no longer exists

Net: ~17.1k → ~8.5k chars of always-loaded context.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 21:14:34 +02:00
m038andClaude Opus 5 ed6e43ae51 docs: trim CLAUDE.md 305->179 lines; extract code descriptions to docs/
CLAUDE.md is loaded into context on every request, so every line has a
recurring cost. Applies one rule to decide what earns its place: keep what
changes behaviour (rules and gotchas Claude cannot discover before it acts);
extract what merely describes code (Claude reads the code anyway, and prose
about code silently drifts).

The four stale facts fixed in the previous commit were all in the
"describes code" class -- active_trip, the Admin2 version, demo-load's
scope, the gitignore list. None were rules. That is the argument for moving
this material next to what it documents.

Extracted (kept as pointers):
- entry-map + trip-feed-col parameter contracts (56 lines) -> reference/
  architecture.md "Shared partial contracts". CLAUDE.md keeps only the
  invariants: single map path, must assign window.tripMap/homeMap, keep
  trip-feed-col single-purpose, initTripStats depends on MapUtils.
- Prod override runbook (49 -> 9 lines) -> guides/deploy-cycle.md "The env
  override tree", incl. the Twig dev/prod table and WEB_HOST. CLAUDE.md
  keeps the two behavioural rules: never commit prod values, and Admin on
  the server writes to the env tree (so check both config paths, env wins).
- GPX API routes, session auth and the Blob/FormData upload gotcha ->
  guides/gpx-manager.md "How the manager is wired".
- Trip-switch procedure -> guides/trip-switching.md. CLAUDE.md keeps the
  one rule that matters: never re-add pageconfig.parent to post-form.md.
- Also trimmed the dev-command table and custom-plugin table added in the
  previous commit; both largely restated the Makefile and blueprints.

Fixed the guides being pointed into, so the pointers lead to truth:
- trip-switching.md instructed editing a pageconfig.parent that no longer
  exists -- its whole "two files must be updated together" premise was
  obsolete and would have reintroduced the desync it warned about.
- architecture.md: Grav 2.0.4->2.0.7, Admin2 2.0.10->2.0.12, corrected the
  posting pipeline to show cache-on-save injecting parent before the write,
  added entry-actions to the custom-plugin list.
- japan-korea-2026 -> denmark-2026 across guides/reference (docs/solutions
  keeps its historical references intact -- those are incident records).

Verified: every markdown link resolves, every referenced section heading
exists, and each extracted item was confirmed present in its new home.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 20:55:28 +02:00
m038andClaude Opus 5 2fbfc884b9 docs(claude-md): fix four stale facts; document testing, dev commands, patches
Audit of the root CLAUDE.md (scored 76/100) found the architecture and
remote-ops coverage strong but the test workflow entirely undocumented and
several facts drifted from the tree.

Corrections (verified against the checkout):
- active_trip was japan-korea-2026; committed value is /trips/denmark-2026
  and no japan-korea trip folder exists. Also note the value is a route.
- Admin2 2.0.10 -> 2.0.12 (installed version).
- demo-load/demo-reset described as italy-only; the Makefile loops over every
  fixture trip under user/docs/demo/trips/.
- user/ gitignore claim omitted the three un-ignored site-owned plugins and
  the secret/env exclusions.

Additions:
- Section 3 "Testing": make test/test-config/test-post/test-ui, the
  auto-created testrunner account, Playwright layout, the auth.setup.js
  storageState dependency, and GRAV_BASE_URL for worktree servers.
- Local dev command table, plus which theme assets are build outputs
  (js/src -> bundles) versus hand-authored (css/style.css, css/tokens.css).
- Custom plugins: story-blocks and entry-actions alongside cache-on-save.
- Local plugin patches: install-plugins overwrites git-ignored third-party
  plugins; deploy/patches/ + apply-plugin-patches is the tracked fix path.
- travel-memories service on 8082; make pixelfed-import.

Every make target and file path referenced was verified to resolve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 20:28:53 +02:00
m038andClaude Sonnet 5 a517331d1b fix(post): cover the code-review fixes; mark location-override plan complete
Adds Playwright coverage for the four cross-reviewer-confirmed bugs
fixed in the user/ submodule (map-load race on rapid reopen, mismatch
flag clearing on blank, and submit blocked on unresolved mismatch),
and bumps the user/ pointer to the commit with those fixes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 20:03:19 +02:00
m038 01c3e72c8f test(post): add location-override Playwright coverage
Mocks the Open-Meteo geocoding endpoint via page.route() so the suite is
hermetic. Covers the panel's closed-by-default state, search happy path,
Paris/Texas disambiguation ranking, no-match/network-failure/in-flight
states, XSS-safe rendering, map canvas singleton behavior, drag sync, the
mismatch flag, the maplibre-gl lazy-load boundary, and a full submit
round-tripping lat/lng into the entry's frontmatter.
2026-07-24 19:38:39 +02:00
m038andClaude Sonnet 5 641b0c376e docs(working): plan — post form location override; doc-review fixes to spec
Adds the implementation plan for the location-override feature and folds in
ce-doc-review findings: a panel-open sync gap (pin didn't render on reopen
with pre-existing coordinates), keyboard/ARIA accessibility gaps in the
search-results list and mismatch flag, a shared MAP_STYLE module to remove
duplication drift risk, and a corrected Open-Meteo risk/mitigation split.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 19:06:50 +02:00
m038 2ab6575e4b docs(working): spec — post form location override (search + map + drag pin)
Addresses the actual root cause behind the Denmark 2026 corrupted-coordinate
bug: there was no supported way to set an entry's location to somewhere other
than the current GPS position, forcing hand-typed/pasted raw coordinates
through Admin2's fragile text field. Backend sanitization (cache-on-save)
already guards against silent corruption; this spec adds a frontend way to
avoid needing that path at all.
2026-07-23 20:39:30 +02:00
m038andClaude Fable 5 94bfc53b90 docs(working): overnight pre-trip readiness audit + product ideation report
Audit: posting pipeline / auth / API surface review with prioritized P1-P3
findings and a morning checklist; P1-1 (prod 2M upload limit) marked
resolved 2026-07-09 via the CGI->FPM switch in Webmin. Ideation: 7 ranked,
repo-grounded ideas (top pick: OG meta + RSS follow-along stack).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195b3cDdMeize2Mm1FgC2aU
2026-07-09 17:59:05 +02:00
m038andClaude Fable 5 0defa85f58 test(post): cover the upload-submit gate and lightbox EXIF-dims invariant
Regression specs for the two 2026-07-09 prod bugs (fixed in user/ e17a5dc):

- upload-gate.spec.js — UG1/UG2: create submit is blocked with a visible
  message while a photo upload is in flight or after it FAILED; nothing may
  land on disk. The form plugin's own guard misses LOADING and
  PROCESSING_ERROR, which silently dropped a photo on a fast save.
- lightbox-dims.spec.js — LD1: a slide's data-pswp-* must equal the
  browser-rendered natural size of the linked image. Fixture is an 800x600
  JPEG with EXIF Orientation=6 (renders 600x800 portrait), planted on disk
  in the demo trip (the active trip may be an unpublished draft that 404s).

New fixture: tests/fixtures/test-photo-exif-portrait.jpg.

Note: the suite currently needs GRAV_TEST_USER/GRAV_TEST_PASS overrides —
the .env GRAV_TEST_PASS contains shell-special chars that break `make
test-account` (see the Makefile comment requiring a plain password).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195b3cDdMeize2Mm1FgC2aU
2026-07-09 17:59:05 +02:00
m038andClaude Fable 5 9ffeb4d2d8 fix(make): build-assets drops privileges via --user; bump CLAUDE.md Grav to 2.0.7
Close the remaining root-owned bind-mount vector: build-assets (a docker
run, missed by the docker-exec fix in 209b804) now runs as the host
uid/gid with HOME=/tmp for npm's cache. Verified: build completes clean,
zero root-owned files under user/themes, bundles byte-identical.

Solution doc updated from "still open" to fixed; CLAUDE.md stack section
now matches the Dockerfile's Grav 2.0.7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195b3cDdMeize2Mm1FgC2aU
2026-07-08 23:41:10 +02:00
m038andClaude Opus 4.8 6cf50920df chore(user): bump pin — taglines, Denmark desc, Past Trips→Trips rename
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:39:42 +02:00
m038andClaude Opus 4.8 60e80c3e72 docs(future): plan — view unpublished trips on frontend when logged in
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:38:49 +02:00
m038andClaude Fable 5 24867524a1 docs(solutions): compound refresh — fix reference drift, grow CONCEPTS.md
Refresh audit of all 13 docs/solutions learnings against the current
codebase. Core guidance verified accurate everywhere; three docs had
reference drift:

- dual-repo-submodule-workflow: point worktree setup/teardown at the
  make worktree-new/worktree-rm targets (manual procedure misses
  .worktree-env isolation)
- docker-exec-root-owned-bind-mount-files: tracked-plugin list now
  includes entry-actions; fix-perms description matches actual target
- grav-plugin-config-without-code-wont-enable: 3-category model's
  custom-in-repo list now includes entry-actions

CONCEPTS.md: add Container, Content repo, Outer repo, Pin, Env tree,
Remote-only plugin; refresh Active Trip (switching is one setting now).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195b3cDdMeize2Mm1FgC2aU
2026-07-08 23:37:56 +02:00
m038andClaude Fable 5 f3816bfc3e docs(future): blueprint vetting research + recommendation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 23:22:45 +02:00
m038andClaude Opus 4.8 084f683e19 docs(plans): mark frontend-entry-edit + photo-editor-media-api complete
Both shipped with feat/journal-post-form (merged + deployed to prod) and
passed owner UI/touch-drag QA on 2026-07-08. Corrected the stale
"not merged / not deployed" language and fixed a duplicate Status marker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:08:20 +02:00
m038andClaude Opus 4.8 62f940f6ef chore(user): bump pin — feature entries + Denmark publish
Content repo f4ab730: featured: true on 3 entries (homepage highlights) and
Denmark 2026 published with its Vestkystruten GPX.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-08 22:30:14 +02:00
m038andClaude Opus 4.8 bc15f0b07d docs(solutions): extend root-owned bind-mount doc to docker run / build-assets
The install-plugins fix (209b804) only covered docker exec. build-assets
runs `docker run node:20-alpine` without --user, so it still writes
root-owned node_modules + esbuild bundles into user/themes/ — which is what
blocked `git worktree remove` at teardown. Broaden the doc and prevention
rule to cover docker run, with the --user fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-08 22:30:14 +02:00
m038 b205db0ea9 Merge feat/journal-post-form: trip publish toggle (tests + docs), bump user pin
Land the owner trip publish/unpublish toggle to local main: Playwright specs
(TP1-TP8), the ce-compound solution doc + CONCEPTS.md Published/Draft concept,
and the plan/spec docs. Bumps the user submodule pin to 543e8e3 (the merged
user/ main containing the feature + denmark-2026 cover content).

Local landing only — nothing pushed.
2026-07-08 17:48:32 +02:00
m038andClaude Opus 4.8 bb2b64bd78 docs(solutions): in-place header edit + APCu cache staleness (Part 3)
Document the trip publish-toggle cache-invalidation finding: an in-place
trip.md `published` edit under cache.check.method: folder + APCu driver stays
stale because the folder checksum is unchanged AND the web APCu store is
unreachable by a CLI clearcache — fixed with apcu_clear_cache() from the web
request. Cross-link the sibling grav-deleteall doc (the create/delete case) as
necessary-but-not-sufficient here, and add the Published/Draft trip status
concept to CONCEPTS.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-08 17:17:22 +02:00
m038andClaude Opus 4.8 2cdb435182 test(trip): cover publish-toggle failure/lock paths + missing-key 400
Add the three cases the code review flagged as uncovered:
- TP7 (R15): a failed POST reverts the switch and surfaces the visible toast.
- TP8 (R13): the in-flight lock suppresses a concurrent second submit (exactly
  one POST fires while the switch is aria-busy/disabled).
- TP5 leg: a MISSING published key -> 400 (the array_key_exists branch, distinct
  from the is_bool branch already covered).

All 10 trip-publish specs green (serial, worktree container).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-08 17:17:09 +02:00
m038andClaude Opus 4.8 500d59bdae chore: bump user submodule pin to 4cc0a18 (denmark cover)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-08 17:16:53 +02:00
m038andClaude Opus 4.8 3e1ddd8132 test(trip): publish-toggle Playwright specs + plan/spec docs; bump user pin
TP1/TP1b/TP2–TP6 cover the owner gate, coverless drafts, cache-correct
hide/restore, the active-trip confirm, backend authz (401/403/400), and
the home fallback. The suite pins site.owner_username to the authenticated
test user (restore on teardown) and runs serially — it mutates global
config and clears the shared cache, so it collides with parallel readers.

Bumps the user/ pin to the finished trip-publish-toggle content (064f0f0)
and marks the plan Complete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-08 14:50:20 +02:00
m038andClaude Opus 4.8 28e57f62c2 feat(makefile): add remote-warmup — clear + warm cache after deploy
A `reset --hard` content deploy leaves Grav's compiled-Twig/page cache
stale, so the first visitor pays the recompile. `remote-warmup` clears
the cache then crawls the public site (homepage + trips listing + every
trip page linked from it) to pre-render pages. Grav has no native warmup
command, so it's an HTTP crawl — which also doubles as a smoke test
(non-2xx pages flagged). Wired into REMOTE_TARGETS (-test/-prod variants)
and added as the final step in both deploy-cycle.md phases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-08 13:05:33 +02:00
m038andClaude Opus 4.8 3d9d3ecb85 docs(solutions): capture docker exec root-owned bind-mount fix
New learning: docker exec defaults to root, so make targets writing into
the ./user bind mount (esp. install-plugins -> gpm) created root-owned
files (11,624 accumulated), breaking worktree-rm. Fix: HOST_UID/HOST_GID +
`-u` on file-writing execs while the grav container still boots as root.

Cross-linked reciprocally with the sibling docker-dev-env upgrade doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-08 12:15:01 +02:00
m038andClaude Opus 4.8 209b804423 fix(makefile): create container files as the host user, not root
`docker exec` defaults to root, so `make install-plugins` wrote plugins into
the ./user bind mount as root — un-removable on the host without a root
container (exactly what blocked the 2.0.4 worktree cleanup). The grav service
can't simply run `user: 1000` because the base image entrypoint needs root to
bind :80 and set up cron, so drop only the file-CREATING CLI to the host user:

- HOST_UID/HOST_GID from id -u / id -g
- install-plugins makes cache/tmp writable (container-internal, never touches
  the host) then runs gpm as the host user, so plugins land owned by you — no
  post-hoc chown, no root files, no root rm needed at teardown

Verified: gpm reinstall as uid 1000 leaves 0 root-owned files under ./user
(was 11624), site healthy (/ and /admin 200), plugin patches reapplied.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-08 11:35:48 +02:00
m038andClaude Opus 4.8 20df900188 docs(plans): refresh journal-post-form status — merged to main
The feat/journal-post-form branch is now merged into main; drop the stale
"not yet pushed / awaiting owner go-ahead" tail from the Status line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-08 10:56:07 +02:00
m038andClaude Opus 4.8 c4891d8f60 chore(makefile): add worktree-new/worktree-rm targets for isolated dev servers
Encode the dual-repo worktree SOP as make targets so no step is skipped:
worktree-new creates the outer worktree off main, inits its own user/
submodule, branches both repos, and starts an isolated Grav dev server on an
auto-picked free port (8090+) whose identity is persisted in a git-ignored
.worktree-env; worktree-rm tears it all down including the submodule deinit
that, when skipped by hand, leaves orphaned .worktrees/ dirs.

docker-compose.yml container_name + ports are parametrized as ${VAR:-default}
so the main checkout is byte-identical, and the 11 hardcoded intotheeast_grav
refs in local targets now use $(GRAV_CONTAINER). CLAUDE.md points at the
commands instead of the manual steps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-08 10:56:07 +02:00
m038andClaude Opus 4.8 793ca10d4d chore(user): bump pin — delete-index fix + gpx-manager listing
Points user/ at 55da834 (entry-actions delete cache fix + gpx-manager shows
unpublished trips newest-first).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-08 10:29:21 +02:00
m038andClaude Opus 4.8 2e96106c84 test(post): DEL4 — a deleted entry stays gone after a page reload
Guards the page-tree-index staleness fix. DEL1 only checked optimistic DOM
removal + disk; DEL4 reloads and asserts the server no longer renders the card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-08 10:29:21 +02:00
m038andClaude Opus 4.8 ceb0570c86 docs(spec): trip publish/unpublish toggle design
Owner-gated publish switch on /trips cards + trip pages; extends
entry-actions with a scope-guarded route + page-tree cache invalidation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-08 09:16:38 +02:00
m038 877d29b2b4 Merge branch 'feat/journal-post-form'
# Conflicts:
#	Makefile
#	docs/working/backlog.md
#	user
2026-07-08 00:10:14 +02:00
m038 fa6550a232 Merge branch 'main' into worktree-content-fixes 2026-07-07 23:52:34 +02:00
m038andClaude Opus 4.8 838f237ef5 docs(solutions): capture Grav cropResize-vs-cropZoom cover-image gotcha
New ui-bugs learning: cropResize fits-inside (returns a source-aspect
sliver), cropZoom crops-to-fill. Using cropResize for a cover/banner
strip hands the browser a portrait sliver that object-fit:cover then
upscales into a blur. Documents the fix, the empirical op comparison,
and prevention (verify Medium op dimensions; guard retina upscaling;
regression-test composition, not just the URL).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-07 23:49:58 +02:00
m038andClaude Opus 4.8 5276768e12 test(trip-cover): cover-strip + retina-cap regression; bump user pin
- trip-header/trips-list specs now assert the cropZoom wide-strip aspect
  and the all-or-nothing retina rule (1x-only for sub-2w sources), and a
  new regression on us-canada-mex-2024 covers the reported portrait-blur
- remove the orphaned AE4 test (no-photos-demo fixture was deleted)
- backlog: full-res pixelfed reimport (luxury quality item)
- plan: record the cover-quality follow-up + AE4 fixture removal
- bump user pin -> 536ca20 (trip-description-hero cover fix)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-07 23:39:32 +02:00
m038andClaude Opus 4.8 44c3a1c32e docs(handover): mark photo-editor Playwright handover complete/superseded
Coverage goal delivered and verified (39 post specs pass on :8091);
superseded by 2026-07-07-journal-post-form-review-handover-and-qa.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-07 23:07:27 +02:00
m038andClaude Opus 4.8 b752178eb4 refactor(demo-load): iterate docs/demo/trips/* instead of per-trip blocks
demo-load / demo-reset now loop over every fixture under docs/demo/trips/
rather than hardcoding each trip, so new fixtures (no-photos-demo and any
future one) are picked up with no Makefile edit. The only per-trip specific —
the source `dailies/` folder mapping to `01.dailies/` in the pages tree — is
uniform across fixtures, so one loop covers all. chown is scoped per fixture
dir (never the whole 01.trips tree) and all copies stay `|| true`.

Verified on the worktree server: a full reset then reload restores both
italy-2026-demo (7 gpx + 12 entries + stories) and no-photos-demo; trip suite
green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-07 23:03:54 +02:00
m038andClaude Opus 4.8 af07ef403c test(trip): add AE4 no-banner coverage + record deferred review findings
- trip-header.spec.js: new U4/R9/AE4 test — a trip with no cover_image and no
  entry images renders its text header with no .trip-header-banner strip.
- Makefile: demo-load/demo-reset now also (un)load the no-photos-demo fixture,
  guarded with `|| true` so runs whose user/ predates the fixture skip it
  instead of failing globalSetup.
- plan: post-review follow-up notes the applied fixes and records the two
  intentionally-deferred findings (macro-owned resolution per KTD2; inline
  toggle script per U4's Approach) so they are not re-flagged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-07 09:13:59 +02:00
m038andClaude Opus 4.8 06f4c25631 docs(handover): review handover + owner QA checklist; mark edit-plan review-complete
Add a review handover (Claude→future Claude: branch state, commit map,
dual-session isolation facts, and the owner-triggered landing procedure) plus
an owner UI QA checklist for the flows the harness can't verify (touch-drag,
interactive photo add/delete/reorder). Update the frontend-entry-edit plan
status: implementation + code-review done; only owner QA + landing remain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-07 09:12:01 +02:00
m038andClaude Opus 4.8 6a73be3e49 test(trip): tighten description-clamp assertion + correct R11 coverage note
- trip-header.spec.js: replace the vacuous `toContainText('finally made
  sense')` (the tail text is in the DOM even while collapsed) with real
  clamp/un-clamp assertions — clientHeight < scrollHeight when collapsed,
  clientHeight >= scrollHeight once expanded — so the test actually proves the
  toggle changes visibility.
- trips-list.spec.js: the header comment claimed R11 coverage no fixture
  provided. Note that R11 (set-but-unresolvable cover_image) shares the exact
  else-branch the R7/AE3 fallback test exercises, so it's covered by
  construction in the shared cover macro.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-07 08:35:41 +02:00
m038andClaude Opus 4.8 e10496afe7 docs(solutions): BUG-001 part 2 — deleteAll doesn't rebuild page-tree index
Document why deleteAll() alone left a freshly-posted entry 404-ing on its
edit-prefill API lookup (regular-pages index keyed on config->checksum, which
survives a create under cache.check.method:folder) and why the fix adds
Cache::invalidateCache(). Note the H1/M8 travelling:true coverage gap as a
tracked residual. Cross-link from bugs-and-fixes.md.

Code review F8 (learnings) + F5 (residual-gap tracking).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-07 08:32:20 +02:00
m038andClaude Opus 4.8 d576487886 test(post): share createPhotoEntry helper and fix cleanup leak
Hoist the duplicated per-spec createEntry photo-fixture into a single
createPhotoEntry() in helpers.js (used by delete-flow, edit-mode, and the
anon-view draft). Register the tag for cleanup BEFORE the awaited 15s
success-toast assertion, so a create that lands on disk but whose toast
assertion times out no longer leaks an untracked entry. Add AE3b covering the
disclosure deviation branch (a non-default toggle auto-expands More options).

Code review F2 (leak), F3 (duplication), F6 (coverage).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-07 08:32:09 +02:00
m038andClaude Opus 4.8 86f9018f73 docs: document trip_header_extras param on trip-feed-col shared partial
Code review flagged the shared partial's CLAUDE.md contract table as stale:
trip-feed-col.html.twig gained a trip_header_extras flag (set at :254, wired
from trip.html.twig) but the parameter table the trip/home callers rely on to
stay in sync had no row for it. Add the row + a note on what it gates and why
home omits it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-06 07:35:18 +02:00
m038andClaude Opus 4.8 7ea90de12b test(trip): specs for trip one-liner, description, banner & home gating
- trips-list.spec.js: one-liner presence/absence, retina srcset, cover
  fallback, alt text (U3).
- trip-header.spec.js: HTD stacking order, expandable description, banner
  fallback, split intact (U4).
- home.spec.js: AE7 — the gated trip-page extras never leak onto the home
  route (asserted mode-independently so it can't race the home-highlights
  suite that toggles travelling in a parallel worker).

Also marks the plan complete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-06 00:02:48 +02:00
m038andClaude Opus 4.8 f4dbac6fc2 test(home,maps): skip travelling-gated H1/M8 with a stated reason
H1 (home journal feed) and M8 (home journey map source) only apply when
config.site.travelling is true — home.html.twig otherwise renders the
between-trips highlights grid, which has neither. They now detect that mode
(.home-highlights-title) and test.skip() with an explicit reason instead of
failing misleadingly, so they still run and validate whenever travelling is on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-05 23:42:55 +02:00
m038andClaude Opus 4.8 d3c17791b7 test(post-form): add edit/delete/anon coverage and fix stale photo-gate assumptions
New specs: edit-mode (ES1 save round-trip + ES2/ES3 prefill 404/500 states),
delete-flow (DEL1-3 happy/cancel/failed), anon-view (AN1 no owner controls,
AN2 draft hidden from anon), photo-editor (live add/delete/reorder). Existing:
P3-P8 now attach a photo to satisfy the create photo-gate; V3 picker cap 4->6.
Full post suite 38/38, stable across parallel (3-worker) runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-05 23:42:45 +02:00
m038andClaude Opus 4.8 7534d7d178 test(post-form): expect zero-padded photo-01..NN filenames
The create path now routes through the shared PhotoRenumberer, which
zero-pads to photo-01..NN; update the Playwright assertions and title to
match the new naming.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 20:14:50 +02:00
m038andClaude Opus 4.8 9295914238 docs: capture history-rewrite-under-live-git-sync + secrets-audit tooling
Update the git-sync secret-exposure solution doc with today's operational
lesson: untracking an already-committed secret under a live bidirectional
sync. Covers the direction:both force-push-revert trap, the freeze-every-
server-first sequence, audit-before-reset (authoritative secret in env/),
the stale origin/main ref + sparse-checkout gotchas, and the ignore:-field
mechanism.

Add Makefile targets that supported the fix:
- remote-secrets-audit: secret-safe (existence + size + git ls-files, never
  contents) audit of config/ vs env/<host>/config secret locations
- remote-content-status: also show the .gitignore diff git-sync regenerates

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:25:32 +02:00
m038andClaude Opus 4.8 8441ce392d docs(plan): photo-editor implemented + server-logic verified; status update
Record implementation completion and the verification split: PhotoRenumberer
unit-verified, build/lint clean, /post + assets serve locally; owner-session UI
verification and on-device touch-drag reserved for the user. Note the server-side
SVG block deferred to the R6 add/delete fast-follow (config/security.yaml is
gitignored).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:25:04 +02:00
m038andClaude Opus 4.8 3cb7dfbd8b make: add remote-seed-api-salt + show HEAD in remote-content-status
remote-seed-api-salt writes a per-host popularity salt into the env
override tree (user/env/<host>/config/plugins/api.yaml) so the api plugin
reads it there instead of appending one to the git-tracked
config/plugins/api.yaml. That appended salt kept the content working tree
perpetually dirty, which aborted git-sync's auto-merge on webhook and
silently broke prod auto-deploy. Salt is generated server-side, never
committed, and re-runs are idempotent.

remote-content-status now prints HEAD so a webhook auto-pull can be
verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 16:53:30 +02:00
m038andClaude Opus 4.8 bb7f4a02ef docs: trip switching is one setting (site.active_trip), not three files
The post form no longer hardcodes pageconfig.parent — cache-on-save derives the
write target from site.active_trip at submit time. Update the "Switching to a new
trip" section and the Trip-architecture note accordingly, and point at the Admin
Site → Active Trip page-picker. Drops the stale post-form.md / system.yaml sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:55:05 +02:00
m038andClaude Opus 4.8 1cf2d12bc7 fix(add-page-by-form): patch Grav-2.0 edit-mode header fatal + deploy wiring
Adding a new photo while editing an entry 500s: add-page-by-form's edit branch
reads existing frontmatter via `(array)$page->header()`, but Grav 2.0's
Grav\Common\Page\Header keeps data in a protected `items`, so the cast mangles
keys and `$original_frontmatter['photos']` is never set → array_merge(null,…)
TypeError. Fix: use Header::toArray() (clean keys, stdClass fallback) + guard the
per-field merge. Grav 2.0.7 does not change this — only the plugin fix does.

add-page-by-form is abandoned upstream (last release 2023-09) and its dir is
git-ignored/GPM-managed, so the fix is tracked as deploy/patches/*.patch and
re-applied after any GPM install/update:
- make apply-plugin-patches (local) — chained into install-plugins
- make remote-apply-plugin-patches-{test,prod} — piped over SSH into
  `patch -p1 --forward`; chained into remote-install-plugins / remote-update-plugins
Content syncs don't touch user/plugins/, so the patch survives them; only a GPM
op wipes it (now auto-restored). Runbook + README document the step and a
verify check. Remove once the plugin is forked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:43:12 +02:00
m038andClaude Opus 4.8 0f6b1e69cd docs: capture local Grav core upgrade + refresh version-authority docs
New learning: docs/solutions/tooling-decisions/upgrade-local-grav-core-rebuild-docker-image.md
— the local Grav core is baked into the Docker image (only ./user is bind-mounted),
so it upgrades by a Dockerfile URL bump + image rebuild + `docker rm -f` recreate,
not the `gpm self-upgrade` the servers use (non-durable in-container).

Refreshed three docs this exposed as stale/incomplete:
- local-setup.md: rewrote the stale "newer Grav RC" section with the durable
  rebuild procedure (recreate gotcha, verify, plugin refresh, non-durability note).
- deploy-cycle.md: Phase 0 now upgrades the local core; state-model notes the
  image as a fourth surface beyond the three server layers.
- stale-grav-version-blocks-api-plugin-install.md: version-authority surfaces
  3 -> 4 (hardcoded Dockerfile URL); clarified .env* GRAV_VERSION governs fresh
  remote installs only, never the local Docker core.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-05 12:05:51 +02:00
m038 10f990e0e7 Merge remote-tracking branch 'origin/main' into feat/journal-post-form 2026-07-05 11:37:28 +02:00
m038andClaude Opus 4.8 fec6a475a2 deploy: bump Grav to 2.0.7 across build image + env pins
Local Docker image was still baked at 2.0.4 while test and prod had
self-upgraded to 2.0.7. Bump the Dockerfile release zip to 2.0.7 and
align the GRAV_VERSION pins so a fresh remote-install lands on 2.0.7
directly instead of installing an older base and needing a self-upgrade.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-05 11:34:21 +02:00
m038 7329852497 Merge remote-tracking branch 'origin/main' into feat/journal-post-form 2026-07-05 11:32:40 +02:00
m038andClaude Opus 4.8 c56265824b docs: entry-edit plan — M2 load/remove/reorder done, R9 (add) deferred
Update Status: M2 partially delivered (U7 load existing photos + remove +
reorder verified end-to-end on :8091, V9/V10 pass). R9 (add new photos on edit)
deferred to the form-to-page/image-upload rework — a new upload on edit fatals
in stock add-page-by-form on Grav 2.0 ((array)$header mangled keys →
array_merge(null) TypeError); that plugin is GPM/git-ignored so it can't be
forked here. Add affordance suppressed in edit mode meanwhile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 11:25:01 +02:00
m038andClaude Opus 4.8 58a8504a47 deploy: mirror prod env override to test (align environments)
test had no user/env/<host>/ tree because deploy/env/test/ never
existed, so `make remote-apply-env-test` hit its missing-source guard
and was silently skipped — test ran with twig cache off and without the
gzip/Content-Encoding fix, hiding the exact class of gotcha that only
surfaced in prod.

Make deploy/env/test/system.yaml a symlink to deploy/env/prod/system.yaml
so both deployed hosts share one override and can never drift; genericize
the prod file header to say it now serves test + prod.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-05 11:12:32 +02:00
m038andClaude Opus 4.8 0a7997c92d docs: refresh stale-grav-version doc — cross-link config-without-code sibling
ce-compound-refresh (scope: stale-grav-version-blocks-api-plugin-install).
Update (not consolidate — distinct triggers, moderate overlap): add a Related
back-link to the new grav-plugin-config-without-code-wont-enable sibling and
broaden the "verify ls user/plugins/<name>" prevention bullet to note the
config-without-code desync as a second non-functional-plugin case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-05 10:44:13 +02:00
m038andClaude Opus 4.8 cf21e199bc docs: add local→test→prod deploy-cycle runbook
Distills the 2026-07 Grav 2.0.4→2.0.7 cutover into a repeatable procedure:
the three-layer state model (plugin code / repo config / host env tree),
ordered per-phase make-target sequences, a smoke checklist that catches the
code-vs-config, stale-version, garbage-page, and git-sync-boomerang gotchas,
plus rollback and one-line invariants. Linked from CLAUDE.md Remote operations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-05 10:40:04 +02:00
m038andClaude Opus 4.8 b5fc43d208 docs: add integration-issue — Grav plugin won't enable when code missing but config persists in env tree
Documents the git-sync prod incident where the plugin was inert despite
`enabled: true`, because its CODE was missing from user/plugins/git-sync/
while its CONFIG survived in the per-host env tree. Fix: GPM reinstall
(make remote-gpm-install-prod PKG=git-sync) + clearcache. Captures the
diagnostic (ls the plugin dir before touching config) and the remote-only
plugin caveat (not in plugins.txt, not restored by the standard flows).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-05 10:34:39 +02:00
m038andClaude Opus 4.8 6dc6af6359 docs: correct git-sync secret-exposure model (gitignore is the boundary, not folder scope)
The prior model claimed git-sync's add-set is scoped to the configured
`folders` (pages/config/themes), so accounts/ and user/env/ were "safe by
construction." That was wrong: prod auto-commit 9337003 pushed the whole
user/env/<host>/config tree (JWT secret, CSRF salt, git-sync token + webhook
secret), accounts/mischa.yaml, and system.yaml to Gitea — all outside the
configured folders. git-sync stages everything under user/ not gitignored;
.gitignore is the only reliable exclusion.

- Rewrite the architecture-patterns doc around the corrected predicate and
  document the incident + remediation (gitignore /env/, rotate token+webhook).
- Correct git-sync-notes.md: env/ is NOT safe on folder scope; drop the
  "never reaches Gitea" claim; fix the secret-files table intro + add /env/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-05 10:19:54 +02:00
m038andClaude Opus 4.8 06d9629075 docs: drop stale form-9.1.10 filepond 'known issue' from CLAUDE.md
Disproven end-to-end on this branch (Grav 2.0.4 + form 9.1.10): file-upload
returns 200, photo persists to disk, POST /post returns 200 with no error
page. The form plugin's upload path is byte-identical 9.1.6->9.1.10, so the
version was never the cause. The original failures were a test artifact --
upload fixtures named as dotfiles (.real-photo.jpg) are rejected by the form
as 'Bad filename'.

The note was also mis-scoped: a 'we're working on it, don't touch' reminder
from the upgrade session belonged in that session's commit/plan, not as a
standing project-wide directive that then told THIS rework branch to stand
down.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1FrCYNq6RXdGYbn5PFrhM
2026-07-05 01:05:45 +02:00
m038andClaude Opus 4.8 7d7346305d docs: update entry-edit plan status — M1 done+integrated, held; M2 deferred
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1FrCYNq6RXdGYbn5PFrhM
2026-07-05 00:44:25 +02:00
m038 fb7b6db1b1 Merge remote-tracking branch 'origin/main' into feat/journal-post-form
# Conflicts:
#	user
2026-07-05 00:43:14 +02:00
m038andClaude Opus 4.8 f0a8895b78 docs: mark frontend-entry-edit plan M1 complete (U1-U6, V1-V7)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1FrCYNq6RXdGYbn5PFrhM
2026-07-05 00:20:51 +02:00
m038andClaude Opus 4.8 e0e2e1e7b5 docs: mark Grav 2.0.4 plan Phase 3 (prod) executed + outcome
Prod was actually deployed (LIVE at intotheeast.com, Grav 2.0.7); the
plan previously said Phase 3 was documentation-only/not-executed. Record
the three deploy gotchas (docs/solutions/) and remaining follow-ups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-05 00:16:33 +02:00
m038andClaude Opus 4.8 39d42119b2 docs: capture Grav plugin-config-must-be-tracked convention (ce-compound)
Knowledge-track: functional Grav plugin config must live in the tracked
user/config/plugins/<name>.yaml override, not the gitignored (and
gpm-update-wiped) user/plugins/<name>/<name>.yaml. Secrets stay in
gitignored *-private.php. Root of the api plugin being unconfigured on
the fresh prod install. Cross-linked with the api/rc.10 install-failure
doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-05 00:05:11 +02:00
m038andClaude Opus 4.8 66438836de docs: capture stale-GRAV_VERSION api-plugin-install failure (ce-compound)
Fresh prod install ran Grav rc.10 (stale .env.prod GRAV_VERSION), so GPM
would not serve the api plugin (needs >=2.0.4); admin2 (auth via /api/v1)
then 404'd login silently. Documents the dead ends (deploying api config,
gpm index -f, same-channel assumption) and the fix (self-upgrade core +
reinstall + bump .env.prod). Adds reciprocal 'same 2026-07-04 cutover'
cross-links across the three sibling deploy gotchas.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-04 23:59:44 +02:00
m038andClaude Opus 4.8 3ad055d4a8 docs: capture Grav double Content-Encoding garbage-page fix (ce-compound)
Non-FastCGI host + Apache mod_deflate: Grav's shutdown early-close emits
Content-Encoding: identity while mod_deflate adds gzip -> two conflicting
headers -> browsers render raw gzip bytes. Fix: debugger.shutdown.close_
connection:false in the prod env override. Documents the dead-end
(cache.gzip/allow_webserver_gzip take the same code path) and the
browser-style curl + screenshot verification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-04 23:53:11 +02:00
m038andClaude Opus 4.8 dcf9c13455 docs: document Grav env-tree config save behavior + git-sync env path
Once user/env/<host>/ exists, Grav Admin saves ALL config (system +
plugin) into that env tree, not user/config/ — so prod's git-sync.yaml
lives at the env path, and Admin-on-server config edits are server-only
(not committed, not synced). Documented the footgun in CLAUDE.md §1
(session-loaded) and rewrote git-sync-notes.md: env-path location,
v3.4.4 folders format, gitignore table (incl. versions.yaml +
security-private.php TODO), and fresh-server enable procedure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-04 23:40:11 +02:00
m038andClaude Opus 4.8 425c7b8e20 chore: bump user/ pin to versions.yaml untrack (4aa9ae9)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-04 23:35:11 +02:00
m038andClaude Opus 4.8 d61de6f3f7 build: make git-sync toggle + diag env-path aware
Grav Admin saves plugin config into the active environment's config tree
(user/env/<host>/config/plugins/) when an env override dir exists — so
git-sync.yaml landed there, not in user/config/plugins/. Update the
toggle script to take a WEBROOT and search both locations (env path
first), and update remote-git-sync-disable/enable to pass WEBROOT.
remote-diag now surfaces git-sync config (secrets redacted) from either
location.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-04 23:35:11 +02:00
m038andClaude Opus 4.8 cc40c23ea8 build: add remote-gpm-install (single out-of-plugins.txt package)
For installing remote-only plugins like git-sync that are deliberately
excluded from plugins.txt. Usage: make remote-gpm-install-prod PKG=git-sync

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-04 23:18:51 +02:00
m038andClaude Opus 4.8 4aeff39756 fix(prod): stop garbage page from double Content-Encoding header
On this non-FastCGI host Grav's shutdown early-connection-close can't
fastcgi_finish_request(), so it emits 'Content-Encoding: identity' to ask
the webserver not to compress — but Apache mod_deflate compresses anyway
and adds 'Content-Encoding: gzip'. Two conflicting headers => browsers
render raw gzip bytes (garbage page). Surfaced when prod flipped to
debug:false today (activated Grav's full output path).

Fix in the prod env override: debugger.shutdown.close_connection:false
skips the identity-header path so mod_deflate compresses cleanly (single
header). Verified: one 'content-encoding: gzip', page renders correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-04 23:12:27 +02:00
m038andClaude Opus 4.8 0e597c5329 chore: bump user/ pin to API plugin config fix (b8a7bed)
Pins the superproject to the tracked API plugin config (deployed to prod
to restore Admin2 login). Already published to content repo main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-04 23:01:36 +02:00
m038 4428ef6c42 Merge branch 'main' into feat/journal-post-form
# Conflicts:
#	scripts/test-form-config.sh
#	scripts/test-post.sh
2026-07-04 22:55:26 +02:00
m038andClaude Opus 4.8 41e61fc148 build: per-environment Grav config override (prod Twig prod-mode)
Prod needs twig.cache:true / debug:false / auto_reload:false, but those
values break local dev, so they must not live in the committed
system.yaml. Add a per-environment override deployed only to the server
via Grav's environment://config (user/env/<host>/config/system.yaml):

- deploy/env/prod/system.yaml — version-controlled source of truth.
- make remote-apply-env-prod — writes it to the server + clears cache;
  resolves the host in-recipe (WEB_HOST || REMOTE_HOST) to avoid the
  recursive-make empty-export trap.
- remote-diag now shows the deployed override + whether twig cache is
  populating, so prod-mode can be verified not assumed.
- CLAUDE.md §1 rewritten: never flip committed system.yaml; use the
  override. Backlog updated (twig prod-mode + /post login-gate done;
  note stale .env.prod GRAV_VERSION and pending git-sync).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-04 22:54:59 +02:00
m038andClaude Opus 4.8 b0cb67a079 docs(backlog): add journal detail-page retire + hero-image cleanup items
Surfaced during the front-end journal entry edit brainstorm; referenced by
the entry-edit plan's Scope Boundaries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1FrCYNq6RXdGYbn5PFrhM
2026-07-04 22:52:20 +02:00
m038andClaude Opus 4.8 553d9e4759 build: force GPM index refresh before install; add remote-diag
- remote-install-plugins now runs 'gpm index -f' before install so a
  fresh server doesn't rely on the stale package index bundled in the
  grav-admin zip (which can miss recently-published plugins).
- Add read-only remote-diag target: Grav version, installed plugin
  versions, GPM view of the api plugin, api override keys, and log tail.
  Surfaced the prod rc.10 vs 2.0.4 mismatch that blocked the api plugin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-04 22:49:06 +02:00
m038andClaude Opus 4.8 c4bee49fc3 docs: add implementation-ready plan for front-end journal entry edit
Two-milestone plan (M1 edit/delete/publish from feed cards, M2 editable
FilePond photos). Enriched from the ce-brainstorm Product Contract to
implementation-ready, then hardened through a ce-doc-review pass:

- KTD1 committed to patching add-page-by-form's edit branch (the
  overwrite_mode:edit create path is unreachable as-is; verified in code)
- owner-username server guards + shared scope-guard helper for both the
  edit save path and the new entry-actions delete route (traversal-safe,
  page-tree resolution)
- missing edit/delete UX states (loading lock, in-flight guards, focus
  management, save-failure preservation) filled in

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1FrCYNq6RXdGYbn5PFrhM
2026-07-04 22:43:09 +02:00
m038andClaude Opus 4.8 c76c16b06d test(post-form): AE3 no longer references the removed hero field
The hero_image blueprint field was dropped (journal heroes come from the
first uploaded photo), so AE3 now asserts the More-options disclosure
toggles open/closed rather than probing the hero input's visibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 21:06:08 +02:00
m038andClaude Opus 4.8 45c2d54d2b test(post-form): assert 1–6 photo rule
Add a test for the ≥1-photo requirement (submit blocked with a photo-section
error) and the max of 6 (FilePond maxFiles). Rework AE4: a corrupt HEIC is the
only "photo", so fail-closed now means submit is blocked rather than posting a
text-only entry. Give the success-CTA test a photo so it can post under the new
rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:59:01 +02:00