15 Commits
Author SHA1 Message Date
m038 e0e68a9ac3 perf(post): lazy-link maplibre's stylesheet instead of bundling it
The engine was already a lazy import(), but its CSS was a static one — the
usual workaround for esbuild never emitting a <link> for a code-split chunk's
stylesheet (R10). That folded the whole vendor sheet into post-form.css, so
every /post load paid for a panel most submits never open, and ~78% of those
bytes were rules for controls this map never creates (popups, geolocate, zoom,
compass, fullscreen, terrain, scale).

Build the vendor sheet as its own css-compiled/maplibre-gl.css and have
location-map.js <link> it at panel-open, concurrently with the engine's
import(). Keeping the file intact rather than hand-picking the ~16 selectors
in use means a maplibre upgrade can't silently un-style the map.

  post-form.css  92,244 → 26,784 raw   (14,528 → 5,631 gzip)

The href resolves from import.meta.url, so it is correct under any Grav base
path without threading a URL through the template — the panel is built entirely
in JS, so there is no element to hang a data-attr on. A failed stylesheet load
resolves rather than rejects: the map degrades to unstyled, never to absent.

The existing lazy-load guard spec already matched every maplibre-gl URL, so it
covers the stylesheet too; its message now says so, and a new test asserts the
other half — that opening the panel does link and apply the sheet.
2026-07-24 22:33:19 +02:00
m038andClaude Sonnet 5 13c76b29a8 fix(post): close location-map race, block submit on bad coords
Code review (4 independent reviewers) converged on the same bug: the
maplibre-gl singleton cached its handle only after import() resolved,
so a fast close/reopen of the "More location details" panel could
race two Map instances onto one container. Cache the in-flight promise
synchronously instead, and propagate/handle import rejection so a
failed map load surfaces a hint instead of hanging silently.

Also closes a submit-time hole the adversarial pass found: the
mismatch flag on lat/lng was purely cosmetic and never blocked
form submission, so out-of-range coordinates could be saved. The
flag now gates submit like the other required fields, and clears
itself when both fields are blanked back out instead of sticking.

The geocode fetch gets a 10s timeout via AbortController so a hung
response can't leave the lookup button disabled forever.

Also moves the location-details CSS out of the site-wide style.css
into post-form's own code-split stylesheet, since none of it is used
outside the post form.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 20:03:01 +02:00
m038 797b472a60 feat(post): add "More location details" panel — search, map, sync
Closed-by-default disclosure below City/Country holding a city/country
geocode lookup (Open-Meteo, city-only query with client-side country
ranking), a single-marker draggable MapLibre preview (lazy-loaded via
location-map.js), and the relocated lat/lng fields. GPS button, search
pick, pin drag, and typed values all stay in sync via syncPinFromFields(),
with a visual mismatch flag for unparseable typed input.

Built entirely in JS (mirrors the existing 'More options' disclosure
pattern) so post-form.html.twig needs no template change.
2026-07-24 19:38:24 +02:00
m038andClaude Opus 4.8 034db29e66 fix(post-form): commit photo thumbnail grid + overlay styling
Renders FilePond items as a 3-up square-thumbnail grid on create+edit,
disables native image-drag so FilePond owns reorder, and hides the
file-info overlay on already-uploaded (idle) items. This is the styling
QA'd and approved on the worktree dev server; it was previously left
uncommitted (mis-tagged as unrelated WIP in earlier handovers).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-07 23:44:20 +02:00
m038andClaude Opus 4.8 a4432d897e feat(post-form): live photo editor on entry edit (media API + SortableJS)
Replace the FilePond photo path in edit mode with our own thumbnail grid that
talks straight to the media API (the gpx-manager pattern). Add/delete/reorder
each persist immediately, decoupled from the form's text-field Save:

- Add: HEIC->JPEG client-side, stock POST .../media per file, then ONE reorder
  after the batch (renumber photo-01..NN). On a failed reorder: auto-retry
  (idempotent), else roll the just-uploaded files back so no orphan stock-named
  image breaks cover=first. Upload progress shown per file.
- Delete: inline 'Delete? [Confirm] [Cancel]' (Confirm disabled in flight),
  stock DELETE, then renumber the survivors.
- Reorder: SortableJS drag -> POST /entry/<slug>/photos/order. On failure the
  move reverts to last-known-good; the shown grid never disagrees with disk
  without an inline error.
- Loading + empty states; first cell badged Cover; photo-NN URLs cache-busted
  since reorder reuses them for different bytes.

FilePond is fully decommissioned in edit mode (initPhotoConversion early-returns
under EDIT_MODE): no stale photo_order manifest is posted on text Save, so
cache-on-save can't delete a live-added photo. Create-mode FilePond is untouched.

Adds sortablejs (bundled into js/post via the post-form entry). SVG excluded in
the file-input accept; the server-side SVG block is a documented fast-follow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:19:57 +02:00
m038andClaude Opus 4.8 265a06a972 feat(post-form): U5 — edit prefill + edit-mode form behaviour
The card Edit link opens /post?edit=<route>. post-form.js now (KTD4/KTD9, D1/D6/D7):
- On ?edit=, disables the form and shows 'Loading entry…' before the fetch (D1),
  so slow-connection typing can't be overwritten by the incoming prefill.
- GETs /api/v1/pages<route> (credentials:include, the gpx-manager session pattern)
  and populates every field from data.header.* / data.content: title, date
  (space→T for datetime-local), content (EasyMDE), lat, lng, city, country,
  weather select, temp, transport, featured, force_connect, published toggle.
- Sets the hidden edit_path to <route>/entry.md so cache-on-save toggles
  overwrite_mode:edit and the save writes back in place (stable URL).
- Hides the photos section and skips the ≥1-photo rule (photos untouched in M1).
- Switches chrome to 'Edit entry' / 'Save changes' (D6); reveals More options.
- On fetch failure, shows an inline banner and keeps the form disabled (D7).
- Skips draft restore in edit mode; carries edit+return on the form action for a
  future re-render (D3/D5). Rebuilt via make build-assets.

Verified in a headless browser: all fields prefill correctly incl. edit_path and
the published toggle, photos hidden, chrome correct (V6); fetch-failure banner
shown with fields left disabled (D7).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1FrCYNq6RXdGYbn5PFrhM
2026-07-05 00:03:33 +02:00
m038andClaude Opus 4.8 d17d1256ba feat(post-form): dark-theme the photo widget + drag-to-reorder that sticks
Styling: repaint FilePond's default light drop zone/thumbnails/actions with
the Field Notes dark tokens so the picker matches the site palette.

Reordering: enable FilePond drag-reorder (allowReorder, itemInsertLocation
'after'). FilePond does NOT re-sequence its submitted data[photos][] inputs on
reorder, so post-form.js sends the visual order as a top-level `photo_order`
POST key on submit. cache-on-save reads it from $_POST (after add-page-by-form
copies the files, priority -100) and renames them photo-1..N in that order —
which the entry honours since it lists media by filename and treats the first
as hero. The order key is top-level (not data[...]), so it never lands in the
entry frontmatter. Best-effort + self-idempotent: locates the new entry folder
by the uploaded filenames and no-ops if they're already renamed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:25:56 +02:00
m038andClaude Opus 4.8 87da3e4b5b feat(post-form): photos-first order, auto-collapsing photo summary, success-only view
- Reorder blueprint to Photos → Title → Content; drop title autofocus so the
  picker leads (it anchors what you write).
- After upload the photo section auto-collapses to a live summary bar
  ("✓ N photos ready — tap to review") and re-expands on tap; the field's
  own .form-label is hidden so the <summary> is the sole header. Trailing
  refreshes settle the summary past FilePond's event/DOM timing gap.
- Replace FilePond's murky completed-thumbnail overlay with a clean green ✓
  badge (remove action left intact).
- On a successful post, hide the reset form + location/weather controls so
  only the "✓ Saved" confirmation + View-journal CTA remain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:46:49 +02:00
m038 d3a520426a fix(post-form): load FilePond CSS in head + real success confirmation
Three field-reported bugs, all root-caused on the isolated test server:

1+2. FilePond's stylesheet never loaded — the filepond field registers it via
   assets.addCss() during body render, too late for the theme's head-only
   {{ assets.css() }}. Photo tiles rendered as giant unstyled boxes that
   stacked and overlapped the rest of the form (Get Location/Weather, Submit),
   making it unusable and looking like upload errors. Load filepond.min.css +
   image-preview CSS in the head_assets block; hide the PQINA credit.

3. The success notice rendered at the top of a long, reset form (off-screen
   after submitting from the bottom) and .notices was unstyled on the dark
   theme. Style .notices; on load, scroll the confirmation into view and inject
   a 'View your journal' link (to site.active_trip) + 'Post another' CTA.

Verified in a browser: 3 photos render compact without overlap; post-submit
shows the confirmation + working view link.
2026-07-04 19:23:02 +02:00
m038 c70e96879b fix(post-form): convert HEIC via FilePond beforeAddFile, not a custom uploader (U4)
Runtime verification showed the custom picker uploaded to Grav's flash but
never attached photos to the entry — attachment needs FilePond's exact
(undocumented) submit contract. Reverting to type:filepond and hooking its
beforeAddFile: a HEIC item is rejected, converted to JPEG via the lazy heic-to
chunk, then re-added with pond.addFile() so FilePond owns upload + page-attach
(the proven path). Web-format photos pass through; conversion failures fail
closed (inline status, original never added). Removes the custom photos field
template + AJAX uploader.

Verified end-to-end in a browser: HEIC posts as JPEG, corrupt HEIC is skipped,
Submit gated while converting, draft photos-reselect hint intact.

Refs R8, R9, R16, R17, AE1, AE4, KTD4.
2026-07-04 17:51:58 +02:00
m038 7f021ab114 feat(post-form): Field Notes styling, disclosure, feedback states (U5)
Migrate Get Location/Weather + required-field validation out of the
template's inline scripts into the bundle: generalize the field lookup so
weather_desc (now a <select>) is found, gate Get Weather until coords exist,
and add idle/loading/success/error states. Build the 'More options' <details>
(native, KTD5) around the advanced trio, auto-open when any has a value.
Add Field Notes CSS: select/number styling, EasyMDE dark theme, disclosure,
button spinner, and server form-message states.

Refs R5, R11, R12, R18, R19, AE3, KTD5.
2026-07-04 16:42:58 +02:00
m038 bf5b5c2f3c feat(post-form): client-side HEIC->JPEG photo picker (U4)
Replace /post's managed filepond field with a controlled 'photos' field
(theme forms/fields/photos) + picker logic in post-form.js: magic-byte
sniff (ISO-BMFF ftyp brands, not filename/MIME), lazy import('heic-to')
only for real HEIC (deferred 3MB chunk via ESM splitting), per-thumbnail
converting/uploading/done/error states, in-flight counter gating Submit,
and fail-closed skip on conversion failure. Converted JPEGs POST to Grav's
AJAX file-upload route into the form flash (the only path copyFiles reads),
so add-page-by-form attaches them on submit. Web-format photos pass through.

Refs R8, R9, R16, R17, AE1, AE4, KTD4.
2026-07-04 16:38:13 +02:00
m038 6282528dd8 feat(post-form): EasyMDE editor + /post-scoped ESM bundle (U3)
Add a page-scoped js/src/post-form.js bundle (built ESM + code-splitting
so U4's heic-to becomes a deferred chunk) loaded only by post-form.html.twig
via a new base head_assets block. EasyMDE replaces the bare textarea with a
minimal toolbar (bold/italic/list/link/preview), FA-free via CSS glyphs, and
syncs codemirror.save() on change + capture-phase submit so the inline
required-field validator and payload see the live value.

Refs R7, R15, KTD2, KTD3.
2026-07-04 15:40:28 +02:00
m038andClaude Sonnet 4.6 a640294e9b build: add map.js bundle — MapLibre GL, toGeoJSON, MapUtils
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-22 23:29:06 +02:00
m038andClaude Sonnet 4.6 5923ba431a build: add main.js bundle — fonts, PhotoSwipe, UI utilities
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-22 23:19:19 +02:00