- 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
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
- 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
- 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
End-of-feature pointer bump for the Grav 2.0.4 upgrade. Pins user/ to
the reconciled Twig 3 strict_mode config, already published to Gitea
main. Aligns the superproject with the finished content-repo state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
Test env upgraded to Grav 2.0.7, plugins to stable, smoke-tested, git-sync
re-enabled. Documents the strict_mode Twig 3 migration gotcha (fresh-image
local build skips the schema migration that self-upgrade runs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
On Admin2-only installs, login new-user auto-detects the admin permission
type to api-only, so accounts lack admin.login and silently fail on
access.admin.login-gated pages like /gpx-manager. Document the root cause,
the --admin-type both fix baked into make test-account, and prevention.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
Update CLAUDE.md + architecture.md stack to Grav 2.0.4 stable, admin2 2.0.10,
gpm.releases: stable, and the three-category plugin-management model
(GPM-managed / custom-in-repo / remote-only git-sync). Add execution outcome,
Phase 3 prod fresh-install runbook (documentation-only), and mark plan Status
complete. Note the Form 9.1.10 filepond regression as a known issue owned by
the separate form-to-page rework.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
Add a dedicated, gitignored testrunner account auto-created by the suite
(make test-account, also invoked from global-setup) so `make test` no longer
needs the real account in .env. The target uses --admin-type both so the
account has admin.login for the gpx-manager specs. test-post.sh switches its
login POST to --data-urlencode to survive special chars in credentials.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
- Task 2 Step 8: version-assert Expected output now includes the
'version:' prefix the grep command actually prints (coherence).
- Rollback: state core self-upgrade is forward-only (no revert) and warn
that server-install.sh must never be used as a live-server rollback —
it rm -rf's the untracked git-sync.yaml encrypted token (feasibility +
adversarial).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
Add docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md
covering the outer-repo + user/ submodule structure, when to bump the pin
(cross-repo feature boundaries, not routine content), the reachable/push-first
rules, and the worktree + per-worktree dev-server flow. Update CLAUDE.md's
folder explanation and add a "Dual-repo submodule structure" section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Converts the orphaned gitlink (160000 with no .gitmodules) into a proper
submodule pointing at the content remote, and absorbs user/.git into
.git/modules/user so linked worktrees can populate user/. Bumps the pin
from 75da83d to the current pushed origin/main (924cfc1) as a clean
baseline. Enables per-worktree user/ checkouts for parallel dual-repo work.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- GPM channel is system.yaml gpm.releases (testing->stable), not GRAV_CHANNEL
- core upgrade is 'bin/gpm self-upgrade -y'; existing remote-upgrade-grav
target ('bin/grav upgrade') is broken and must be fixed
- add server content-pull + cache-clear + preflight steps to Phase 2
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Converts the orphaned gitlink (160000 with no .gitmodules) into a proper
submodule pointing at the content remote, and absorbs user/.git into
.git/modules/user so linked worktrees can populate user/. Bumps the pin
from 75da83d to the current pushed origin/main (924cfc1) as a clean
baseline. Enables per-worktree user/ checkouts for parallel dual-repo work.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Coordinated upgrade of core rc.10 -> 2.0.4 and promotion of
admin2/api/flex-objects to GPM management (option B). Documents the
dependency-forced atomicity, the three plugin-management categories,
git-sync handling (disable-during-upgrade, remote-only), and the
local -> test -> prod(documented-only) rollout.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enriched from the requirements-only brainstorm to implementation-ready:
7 units (active-trip parent injection, full field exposure, EasyMDE,
client-side HEIC conversion, Field Notes mobile styling, draft resilience,
tests). Applied headless doc-review fixes plus two owner decisions:
/post bundle built ESM + code-split (defer heic-to for cold-load), and
R20 draft persistence narrowed to text (photos re-picked after re-auth).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capture the safe procedure + three non-obvious traps from the standalone-
page cleanup: keep the folder as a routable:false data container, repoint
Back-link fallbacks to the grandparent (silent direct-landing regression),
and sync the gitignored demo source + Makefile or the next demo-load undoes it.
Seed CONCEPTS.md (Trip, Entry, Story, Active Trip) and surface docs/solutions/
+ CONCEPTS.md in CLAUDE.md so future sessions discover them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace 'Shared feed-map partial' section with 'entry-map' partial + the
single MapUtils.initEntryMap map path
- Trip architecture: two content subfolders (dailies/stories as inert
routable:false containers); map/stats inline on trip page
- Fix stale references to deleted map/stats/dailies/stories templates in
architecture.md (template list, page tree, GPX flow) and trip-switching.md
(scaffold + verify steps)
- Correct GPX rendering: toGeoJSON bundled in map.js, no leaflet CDN
- Pre-launch smoke test now checks the trip page feed, not /dailies
Left docs/working/* (milestones, learnings, QA, plans) as point-in-time history.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Split env config into local vs remote:
- .env: local/shared config, always loaded (docker compose + make test)
- .env.test / .env.prod: full remote config, loaded on demand via ENV
Remote targets now generate -test/-prod variants (e.g. remote-install-prod);
a guard-env prerequisite blocks bare remote targets with no environment set.
Refresh .env.example to document the two-tier layout and add .env.prod/.env.test
to gitignore and the never-read list in CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1 cleanup companion — demo-load no longer creates the retired
map/stats sub-pages or copies their now-deleted source .md files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Status → Complete; document the markLatest opt added during execution,
the window.tripMap/homeMap exposure, and the test outcome (38 passed;
M6/H1 pre-existing and out of scope).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BftDn9vu9SonFAY4vxu4uk
Plan for extracting duplicated MapLibre init into a shared
MapUtils.initEntryMap(opts). Incorporates ce-doc-review findings:
card-absent fallback invariant scoped (no-op→navigate), exact
isLatest predicate, and the home-map attribution restyle documented
as an intentional change. Filter-hidden-card click left as a noted
known limitation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BftDn9vu9SonFAY4vxu4uk
Adds a 'Shared trip-feed-col partial' section (callers, param table, the
home-predeparture sibling, and the initTripStats JS glue) and points the
trip-page filter-bar note at it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BftDn9vu9SonFAY4vxu4uk
Converge home-active feed-col chrome (date range, filter bar, stats/
cycling panels) with the trip page via a shared partial + shared
initTripStats() glue. Map convergence deferred to a later spec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BftDn9vu9SonFAY4vxu4uk
Assigns Status to the 18 plans that had none, based on checkbox
completion state, memory entries, and CLAUDE.md documentation:
- 16 marked Complete (dates approximate from git/memory)
- 1 marked Not started (template-refactor, now unblocked)
- 1 marked Deferred (pixelfed-import, superseded by travel-memories)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Defines Status field values (Not started / In progress / Deferred /
Complete / Abandoned) and scanning rules so any session knows which
plans are open vs done without reading every file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
- Wrong active trip slug in intro (japan-korea → us-canada-mex-2024)
- Home page URL /home not /
- TC-M.3: describe teal-fade animation precisely, not "background flash"
- TC-M.6: arrows hidden by hover:none (touch devices), not by screen width
- TC-M.7: add note that home feed only renders in trip mode; explain how to verify
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
- HammerJS swipe cards (right=journal, left=skip, up=story) with tilt/color feedback
- Three tap buttons as swipe alternative (J/S/X)
- Undo stack (max 10) with Back button
- Progress bar + header counter sync
- Thumbnail strip (all photos, colored dots, tap to jump)
- Desktop: J/S/X badges on all tagged photos including skip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On viewports < 768px, show a Tinder-style card UI instead of the photo
grid. Cards show one untagged photo at a time with swipe gestures
(right=journal, left=skip, up=story), colour overlays during drag, tap
buttons as alternatives, a progress bar, and a 10-deep undo stack.
Desktop grid is unchanged, wrapped in #desktop-view.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix D: apply _sanitise_slug() to grav_trip_slug in POST /select before
storing in TripState, preventing path traversal via ../sequences.
Fix E: add _yaml_str() helper that doubles single quotes; apply to title,
location_city, and location_country in both run_export and overwrite_export
frontmatter blocks, preventing invalid YAML for values like Xi'an.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Six Playwright tests covering server-side state persistence across hard reloads,
stale-phase propagation after back-navigation, stale banner dismiss, exported group
immutability, and notes surviving phase navigation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements GET /export summary view and POST /export/run which downloads
originals from Immich, writes entry.md with YAML frontmatter, and sets
group status to exported. Includes POST /export/overwrite for single-group
re-export. All 42 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /write now checks all groups are written/skipped before showing
the completion screen; incomplete sessions are redirected to the first
draft group
- POST /write/done now accepts form data (not JSON) and redirects to
/export; wired up from the completion screen via a <form> POST button
- phase5.html extra_scripts block wrapped in {% if group %} to prevent
Jinja errors when group is None on the completion screen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>