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>
- P6: verify "Entry posted successfully!" toast after submit
- P7: verify server resolves default:now to a recent timestamp in saved frontmatter (Grav renders the literal "now" string in the HTML input; resolution happens server-side)
- P8: verify title/content fields empty after successful submit (form reset:true)
Also fix pre-existing helpers.js issues:
- TRACKER_DIR now resolves via docker inspect or GRAV_USER_DIR env var so tests find entries even when running from a worktree without a user/ directory
- DAILIES_URL exported and derived from post-form.md pageconfig.parent so P1/P2 navigate to the correct active-trip URL
- cleanupEntry/findEntry now guard against missing TRACKER_DIR
- P2 marked test.skip (was running and failing on missing fixture)
Add group.py route, phase4.html template, and supporting state changes.
Photos are shown as a flat stream; clicking divider zones inserts
entry-break boundaries that split photos into labelled groups. Labels
persist via group_labels dict. Done materialises groups into state.groups
and advances to write phase.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename /curate/retag to /curate/swap; response now includes new_tag
- /curate/reorder: read body["order"] key (was ordered_ids); include date field
- /curate/remove and /curate/swap: return 404 if asset_id not found
- Update phase3.html JS fetch calls and reorder payload to match spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Also fix auth.setup.js AUTH_FILE path: the file lives in tests/ui/auth/
so the relative path to tests/.auth/user.json needs ../../ not ../ to
match the storageState path in playwright.config.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM