Commit Graph

189 Commits

Author SHA1 Message Date
Maurice 2d398568da fix(assistant): no status request while neither AI addon is on
Every page of an install with the assistant and AI parsing off asked /api/ai/status and logged the 404 the addon guard answers. The store asks only while an addon opens the route and the install is not managed, and one hook on the desktop window and both phone screens asks again when the assistant or AI parsing is switched.
2026-10-25 09:48:37 +01:00
Maurice 4b633d8cd2 feat(admin): release notes show a #### heading as the feature it names
The release notes in Admin read ## as the version and ### as a section,
and printed a #### line as plain text. A release that heads each feature
inside a section with #### (Tours, TREK Studio under Added) now shows it
as a feature heading between the section and its text, on desktop and on
the phone.
2026-10-24 18:28:44 +02:00
Maurice 429a25faa9 fix(admin): name the addon columns by start and end 2026-10-24 15:10:13 +02:00
Maurice 486a2eeca3 feat(admin): addons tab in the cards of the other tabs, with a search and an on/off filter
The addons sit in a card per type now, the way every other admin tab sets
its cards, instead of three columns nested in one card. A head card shows
how many of each type are on and finds an addon by its name or description,
with a switch between all, the ones on and the ones off.
2026-10-24 15:10:13 +02:00
Maurice d07b8ba56e feat(assistant): web search through the provider, and cards with a way back for the smaller tour writes
- Web search: ChatGPT (Responses web_search), Anthropic (web_search server
  tool) and OpenRouter (web plugin) search the web themselves when the admin
  switch allows it (on as it ships). A search shows as a step ("Searching the
  web for ..."), the cited pages as source chips under the answer. Anthropic
  answers that searched are replayed with their blocks (new provider state
  adapter); the search step never goes back to the model as a call. The
  prompt says when to search and that chosen places still go through
  search_place.
- Tour writes: assign_tour_track (undo: take that assignment off),
  unassign_tour_track, set_tour_activity_excluded (undo: the other switch),
  link_tour_packing and unlink_tour_packing (undo from the link before, which
  both answers now name) get change cards and their ways back.
2026-10-23 19:13:51 +02:00
Maurice f0a3fe9dbb fix(assistant): green CI round after the shared ChatGPT plan
- Tests that still knew the ChatGPT sign-in as personal only follow the
  admin's shared plan: the service takes the caller with their role, the
  admin tiles show ChatGPT, the controller tests cover the admin routes.
- The table refused an instance ChatGPT connection with a CHECK, so
  sharing the plan failed. A new migration rebuilds ai_connections
  without that check; the shipped migration stays as it was.
- useChatgptConnect grew past the function size limit: reading the
  clipboard and noticing the return to the tab are helpers of their own.
- The file link factory hands its overrides over apart from the row,
  like every other factory, which ends the deep type instantiation.
- Knip: exports only their own file used are private again, and the
  booking cap reads MAX_WRITES_PER_STEP instead of an alias.
- lint:i18n-keys reaches the tools' own action names through a key
  builder and the connection hints through literal keys.
- MMehrSheet stays in the assistant hints pair only.
2026-10-22 21:30:38 +00:00
Maurice c37d3f773e fix(assistant): readable cards for any tool, and the CI round
- A card for a tool without one of its own names what it works on: the place
  of an assignment, the day, the note, the booking; its arguments sit under
  the labels the other cards use, a new order reads as the names in order.
  28 frequent tools carry their own action name in every language.
- The server's own steps without a card still refuse, as before.
- Tests: the rate limit is asked one request after another, the mention list
  closes on the caret move a browser reports, the place factory hands its
  category over as a plain id, eleven files formatted.
2026-10-22 21:30:37 +00:00
Maurice 4641788fc9 fix(assistant): lint, types and tests for the CI round
- agent loop and decisions without await in loops: steps as recursion,
  calls and applied changes in order through one promise chain, the
  independent reads and claims in parallel; part mapping in its own helpers
- place card, header and weather strip without refs or components created
  in render; long hooks and components split; state reset during render
- trip notes only in a turn's tool session, never an undo's; map view
  reported only while the assistant can run
- place strip scrolls through the strip's own smooth scroll, no physical side
- tests follow the raised limits and the new card fields; toasts read through
  a mocked useToast instead of a window global; baselines lowered where the
  counts fell
2026-10-22 21:30:35 +00:00
Maurice d24db65cb8 fix(assistant): green CI round, trip notes the assistant can keep, calmer change cards
- the assistant keeps facts for a trip (remember_for_trip) as a change card,
  applied into the user's trip notes; existing expenses can be changed
- change card fields as a few icon chips with the rest folded under Details,
  the day band marks only what changes, the week grid as compact tiles
- no map button on receipts of things that have no place on the map
- tests follow the composer's accessible name, the receipt's status icon and
  the GL map's container; the hanging stream helper honours an early abort
- Postgres probe: qualified columns in the upsert updates; entities restored
  to the generator's output; strict ratchet reads the SDK client as untyped
2026-10-22 21:30:26 +00:00
Maurice 123e27643d feat(assistant): one AI tile for assistant, parsing and MCP, a planner-aware chat
The three AI addons share one tile in the integration column; a dialog holds
the connection, the assistant's limits and AI parsing, which now reads with
the assistant's connection unless an install keeps its own. Existing parsing
configs stay where they are and keep winning.

The chat sees the map on screen, offers next questions under an answer,
shows its live step in the window head, previews waiting changes inside the
day list, checks whether a day still works, folds a multi-day plan into a
week grid, keeps per-trip notes and can estimate costs. An empty or cut-off
answer gets one push instead of ending silently, and the assistant's own
step and size limits are raised.
2026-10-22 21:30:26 +00:00
Maurice b64da26651 feat(assistant): trip assistant chat with your own AI connection
Adds an in-app assistant that reads and plans the open trip. Users connect
their own provider (Sign in with ChatGPT, or an Anthropic, OpenAI, Gemini or
OpenRouter key) or use the one the admin set up, including a custom
OpenAI-compatible server. It is an addon and only shows while a connection
is usable.

The chat runs on the existing MCP tools, pinned to the trip. Every change is
held as a change card the user applies or discards, and applied changes can
be undone. On the desktop it is a floating window that can be moved and
resized; on the phone it is fullscreen. Search results show as place cards
that can go onto a day, weather lookups as a strip, proposed places as
ghost markers on the map with a before/after switch, and an answer can be
shared into the trip chat. Booking files can be dropped in and are read
into change cards.
2026-10-22 21:30:12 +00:00
Maurice 31859651d7 feat(help): rewrite the help center for v5 with a Tours section, new pictures in every language, and fix the tour planner bugs it surfaced
Every help screen checked against the v5 redesign. Tours is documented for
the first time, as a screen of its own with twelve screens under it, and
notifications get help of their own. All pictures are taken again on a
realistic demo trip, with short loops where a still cannot show the motion.
The English is plain and every language is written naturally rather than
word for word.

The switcher shows two levels at most, a screen's overview lists its
sub-screens as cards above its guides, each wiki page is linked once, and
the help sidebar is wider.

Fixes found along the way: Save to Collection keeps a tour's route, a track
dropped on the places column opens the Tours import, tour Share on desktop,
the unsaved tour draft lives on the account, a route click no longer adds
an end point, Trip places leaves the trip's tours out, Escape closes a
stage's day menu, confirms before destructive deletes, DWD warnings sharing
one id kept apart, a booking's date moves with it, and smaller fixes across
notifications, bookings, the day plan, stays, Atlas, Collections, Vacay and
the admin screens.
2026-10-22 22:29:35 +02:00
Maurice 8e420cc1f1 fix(mobile): split the new phone sheets and cards below the lint limits, follow the tests to the new rows 2026-10-22 21:43:15 +02:00
Maurice 49afee161d feat(mobile): phone styling for tours list, trip settings and admin cards
The tours list in the places browser now uses the same rows as places: a
grade or type tile, the day plan's figure badges and a day badge or plus on
the right. A shared "Planned on desktop" badge replaces the grey hint box
there and sits under the road trip's empty stage.

Trip settings and trip edit open phone sheets instead of the desktop
dialogs; the edit sheet gains the day count of a dateless trip and the
reminder. Questions are asked in phone sheets, not the browser.

Admin on the phone: the paddle regions and school holiday cards are phone
cards on their shared hooks, long addon descriptions fold to two lines,
the addon header keeps the wordmark inside its sentence, the group bands
drop the dash and the permissions actions get a row of their own. The
Tours addon gets its catalog name and description in every locale.

The journey map on the phone shows every layer without the switch bar.
2026-10-22 21:26:41 +02:00
Maurice 7fa8b3fe35 fix(tours): keep the day menu items with the stage logic their views import
The pair rule wants TourDayAddButton to import stageSpread; the items it
builds belong there anyway. The addon manager test answers the Tours
switches, so a failed list is the only thing that toasts.
2026-10-20 22:54:57 +02:00
Maurice d3f574f225 fix(tours): match a saved ski route by its ski choice, and keep the planner under the lint limits
The saved ski route compared against an undefined skiKey, so opening a saved
ski tour threw. The held seed now lives in useSavedSkiSeed and is matched by
skiRouteKey(skiChoice). The ski route facts move to skiRouteFacts.ts so the
summary and leg rows only export components, the rail renders
SkiPlannerSummary, and openTour, useTourRoute and useAddonManager shed the
branches and lines the new ski code had added.
2026-10-20 21:43:46 +02:00
Maurice 2d0c0597b3 feat(tours): plan ski tours on the piste router in the planner
A ski tour with a discipline now sends its legs to the piste router and
takes back runs, lifts and trails: each stretch is coloured by its grade
in the area's scheme, links and lift rides down are dotted, and a leg the
router cannot plan stays straight and unrouted with its reason and the one
change that would plan it. Snowshoe and winter hike legs off the piste
data follow summer paths instead, said so. The router's time and heights
are used once every leg is planned, and the options, the build and the
time are saved with the tour.

- skiRouteRepo and api: one request per tour, batched, remembered unless
  the answer may change, offline legs failed as offline
- planner: router options per discipline, leg steps and failure rows with
  fixes, a piste route summary, draft and reopen keep the options
- admin: switch for the piste router under Tours
- i18n: new ski keys in all 27 locales, ski info and time texts rewritten
2026-10-20 21:15:07 +02:00
Maurice 73237ffedb feat(tours): bike tours in the planner, CyclOSM, and the planned cards' last points (BIK-01 to BIK-06, RTE-01, IO-01, LIB-02)
The tour type is a dropdown; a bike tour picks its profile, routes for it,
says when a point lies off every way, and shows what it runs on: surfaces,
kinds of way and, for a mountain bike, the trail grades, each pointed at on
the map. A type's own map (OpenHikingMap for a hike, CyclOSM for a bike tour)
is offered while a tour of that type is open, with an admin switch each.

From the planned cards: a hike avoids steep climbs, steps or ferries, nothing
else; a saved tour can be shared where the platform takes GPX files; and the
tours rail adds a tour from one menu: plan new, import GPX, or take one over
from another trip.
2026-10-19 21:33:49 +02:00
Maurice 06e6e25b25 feat(tours): OpenHikingMap as a hiking base map of the tour planner (MAP-01)
While a hike is planned, the planner's map switch offers "Hiking" beside the
map, topo and satellite: OpenHikingMap, with marked trails over the terrain.
It is a choice of the planner's view alone, like topo: the global map
setting, the tour and its days stay as they are. With no tour open, over an
import viewed as it came in and for a paddle tour it is not offered, and a
choice made earlier falls back to the standard map there.

openmaps.fr's tile usage policy is followed: tiles load only as the map
shows them, with the browser's Referer, from the server's own caching; no
service worker rule keeps them and the 3D flyover's warm-up passes their
source by. Its credit and donation link are on the map; the 3D map, whose
attribution is folded away under the panel, shows them as a line of its own
above its controls.

The service is best effort. A run of failed tiles while it is shown marks
the switch, with a tooltip and a status for screen readers that say it is
not answering and that the tour is untouched; one tile arriving clears it.
The tile health the seamark overlay had is now useTileHealth, shared by both.

The source is free for non-commercial installs up to 500,000 tiles a month.
tours_hiking_map_enabled is an admin switch under Tours (admin tours-hiking-map,
audited), on by default, for an install that is commercial or larger to
switch off until it has an agreement with the operator. /api/addons carries
it as toursHikingMap, true only while Tours is on.

Leaflet, GL and the 3D map read one table of the planner's raster bases
(tourRasterBases), so topo and the hiking map are drawn the same way.
tile.openmaps.fr is on the CSP's connect-src for the GL maps.
2026-10-19 13:07:36 +02:00
Maurice 1bc82f97fd feat(tours): route paddle tours on the water, shaping points and auto-loaded regions
Paddle tours are planned on the water by TREK's own router: a water network
per paddle region, built from the same Geofabrik extract as the obstacles and
searched in a worker thread of the server (no sidecar). Rivers, canals and
lakes (direct or along the shore), portages round weirs, locks by policy,
whitewater by boat, gaps marked. Found portages and locks become points of
their own; an unplannable leg says why and offers a way out.

- Shaping points: a click on the line sets a point that steers the route
  without being a stop (hikes and paddle tours); recompute a single leg.
- Lake style direct/shore, suggested from the wind forecast.
- Regions load by themselves where a paddler plans (admin setting: automatic,
  on request, off; size cap and a daily count), with requests for the admins,
  a notification, and the load followed on the leg.
- Portage from the last point, portages follow footways via the walking
  router (or stay straight), "put in here" on slipways and put-ins.
- MCP plan_paddle_route with the REST route's rules.
2026-10-18 21:21:32 +00:00
Maurice 80b6739362 feat(journey): a trip can link a journal and open it to the members it picks
A trip can now show one journal from the Journey addon as its own: any
journal its owner picks, and one journal on as many trips as they like.
The trip members chosen for the link get into the journal as viewers or
editors, see it in their own journal list and follow it live, for as long
as the link holds them and they stay on the trip.

That access is never copied into journey_contributors. It is read where
journal access is read: the contributor repository answers with the
stronger of a direct role and a link role, the journal list and the live
update recipients take the link members in, and the photo access check
goes the same way. Every one of those reads starts from linkReach, which
only counts members still on the linking trip, so unlinking, deselecting
someone and their leaving the trip all end exactly what the link gave.
A member who leaves is also taken off the link, so coming back later does
not quietly reopen the journal.

Linking or changing the members takes the journal's owner and the new
journal_link trip permission (trip owner by default); unlinking takes
either. /api/trips/:tripId/journal answers the link as each member may see
it, /candidates says who a journal would be opened to for the first time,
and four MCP tools do the same over the same service. A trip:journal:changed
event tells the trip to look again.
2026-10-18 15:59:33 +02:00
Maurice 2e3fd4fc4c test(admin): cover the paddle regions hook: polling while a region is read, the debounced catalogue search, the actions and offline 2026-10-16 23:14:26 +00:00
Maurice ccfb610aab test(tours): cover the paddle obstacle card, map layers, route band, notice, sync, repos and the admin regions panel
Thirteen client suites for the paddle obstacle work and the region admin: the detail model and card, the overlay with its outside press and Escape, the Leaflet and GL layers with the area zoom threshold and the single click handler, the route band marks, every state of the obstacle notice, the sync hook, the tiled obstacle repo, the online only region repo, and the regions panel. The wind hook for the start of a paddle tour is covered too.
2026-10-16 23:12:41 +00:00
Maurice 03d6457bde feat(tours): paddle obstacle card, admin regions panel and obstacle marks on the route band
PAD-05B: a click on an obstacle on the map or on the route band opens its card in the look of the via ferrata card: the kind with its glyph, the name, how it stands to the route (on it at a distance along it, near it with the link marked unclear, or only in the area), the paddling rule its tags give with canoe read before boat, a condition as written, height, grade, hours and fees, the source with the date of the data and the OpenStreetMap link. A missing rule reads as unknown, never as allowed, and a portage way recorded as private says so. Objects only in the area show from zoom 13 on, on Leaflet and GL alike; on GL the lines only near the route are dashed as on Leaflet, one click handler picks the marker over the line, and a picked marker no longer flickers. The route band marks the obstacles on and near the route by their share of the measured line. The obstacle notice also names an answer cut at its limit.

PAD-05A: the admin area gets a Paddle water data card under the addons, on desktop and phone: a search in Geofabrik's list with each region's path, the regions with their state (waiting, reading with its progress, ready with objects and data date, failed with the reason), reading again and removing after a confirmation. The list follows a read without reloading. Region changes write audit rows.

Map colours of paddle objects and portages come from theme tokens read at draw time, as the via ferratas do; the ferrata layer now shares that reader. The rail takes the planner page's parts as one bag, the map hit tests of the tour layers are one helper, and the unused noWaterData string is gone from every locale.
2026-10-16 23:03:09 +00:00
Maurice f1c437b8b8 Keep api/client.ts and trips.service.ts within their size baselines
The admin switch for wanderer moves to api/wanderer.ts and the recorded
track restore to api/recordedTrack.ts; trips.service.ts keeps dev's
formatting with only the derived-from remap on copy.
2026-10-15 18:16:25 +02:00
Maurice 782e613506 Tours: day GPX import, wanderer trails and planning over an import
- GPX files drop onto the map or a day; the import lands on that day
- wanderer connection per user in Integrations; the workspace offers the
  user's own trails only, behind an admin switch under the Tours addon
- wanderer difficulty is stored on the SAC scale (T1 to T3), backfilled
- a GPX or wanderer import can be planned as an editable tour, either in
  place (the recorded track is kept and can be restored) or as a new tour
  that remembers the import it came from
- rails, rows and detail dialog follow the plan sidebar's cards and badges
- admin addon shelves with more than one row fold, collapsed by default
- reverse-geocoded waypoint names are cached on the device
2026-10-15 17:44:15 +02:00
Maurice dae20c166a fix(admin): keep an Anthropic Messages gateway as the AI parsing base URL (#2655)
Both admin forms hid the field for Anthropic and the shared hook saved an
empty address, wiping a gateway configured on the server. The field shows for
every provider now and is saved as typed; switching provider empties it, so an
address meant for one never receives another provider's key.
2026-10-14 13:24:27 +02:00
Maurice a9a0478c63 test(llm-parse): an unreachable ollama shows the server's reason in the addon settings, not the status line 2026-10-12 22:18:50 +00:00
Maurice 66e574fcc0 fix(llm-parse): the local model list and pull send the saved api key to an ollama behind an auth proxy
The admin screen's model list (/api/tags) and pull (/api/pull) went out
without the Authorization header that the extraction and /api/show already
send, so an Ollama behind a Bearer proxy answered 401 and the list came back
as a 502. The controller now adds the key saved with the local provider, read
server side through LlmConfigResolver.readLocalServerAccess, and only when the
requested base URL reaches the same server root as the saved one, so another
address in the form never receives it.

A 401 or 403 now says whether the key was rejected or none is saved for that
address, and the admin form shows the server's reason instead of the axios
status line. A server without /api/tags (LM Studio, vLLM) is asked for its
OpenAI-compatible /v1/models instead, and the list body is read with a size
cap and checked before use.

Refs #2649
2026-10-12 21:04:23 +02:00
Maurice 24461ecf76 fix(admin): the default map template offers the Amap presets users can pick
In the admin's default user settings, the map template dropdown listed
seven tile presets, while every user's own map settings listed nine: the
same seven plus Amap and Amap Satellite. An admin running an instance for
people in mainland China could not pick the one basemap that works well
there as the default, short of pasting its URL by hand.

The Amap presets (57eac32f9) were added to the two user preset copies and
not to the admin's separate copy; nothing in that change or later gives a
reason, and the server stores any template as a default. The later
refactor kept the gap as DEFAULT_MAP_PRESETS beside MAP_PRESETS. There is
now one MAP_PRESETS list, read by the user settings and the admin defaults
on both desktop and phone.

Regression test FE-ADMIN-DUS-029 (DefaultUserSettingsTab.test.tsx) was red
on the old code: "Unable to find role="button" and name "高德地图 (Amap)"".

Changed assertions:
- useMapSettingsForm.test.ts FE-COMP-MAPFORM-001: it expected the admin list
  DEFAULT_MAP_PRESETS to hold 7 presets and the user list to add Amap on
  top; it now expects the one list to hold 9 presets including both Amap
  templates.
2026-10-09 19:00:47 +02:00
Maurice df7bb9a5e1 fix(storage): Test on a mirrored phone storage row probes the backends on the form
On the phone storage panel, Test on a mirrored backend sent the mirror
itself to POST /api/admin/storage/test. The server expands a mirror by
looking its primary and replicas up by name in the SAVED storage
config, so the phone tested the saved settings instead of what the
operator had just edited, and a replica added but not saved yet made
the whole test fail with "references unknown backend". The desktop
probes the primary and each replica with their draft options and
merges the results.

Cause: the phone called admin.test with the mirror candidate, while the
desktop called admin.testMirror with mirrorProbeTargets. The choice
lived in each shell's markup.

Fix: useStoragePanel now offers loaded.testRow(row), which tests a
plain row as it is and a mirrored row through admin.testMirror with
mirrorProbeTargets from the draft. Both shells call it.

Red test: FE-MOB-MSTOR-018 (new, MAdminStoragePanel.test.tsx) failed
with "AssertionError: expected [ 'mirror' ] to deeply equal
[ 'backups-local', 'off-box' ]". FE-ADMIN-STOR-PANEL-017 (new,
useStoragePanel.test.ts) covers testRow itself.

No existing assertion changed.
2026-10-09 19:00:46 +02:00
Maurice 8dd8635b87 fix(plugins): "Update all" on the phone updates the plugins one after another
On the phone plugins panel, "Update all" fired every pending update at
the same time. Only one row can show as busy, so the other rows stayed
clickable while their own install was running, and the list reloaded
once per plugin as each update finished. The desktop runs the updates
one after another, and the hook's own comment calls the parallel way
wrong.

Cause: usePluginsAdmin kept the phone's parallel loop behind the
updateAllInParallel option, which only the phone set.

Fix: the option is gone; updateAll always waits for each update before
starting the next, on both shells.

Red test: FE-ADMIN-PLUGINS-HOOK-009 (usePluginsAdmin.test.ts) failed
with "AssertionError: expected [ [ 'a', undefined ], …(1) ] to deeply
equal [ [ 'a', undefined ] ]".

Changed assertions in src/components/Admin/plugins/usePluginsAdmin.test.ts:
- FE-ADMIN-PLUGINS-HOOK-009 (renamed from "with updateAllInParallel
  every update starts at once, without a version" to "Update all starts
  only the first update, without a version"): pluginUpdate calls were
  [['a', undefined], ['b', undefined]], now [['a', undefined]]. It and
  the mounted() helper no longer pass the removed option.
- FE-ADMIN-PLUGINS-HOOK-008: only its name lost "by default".
2026-10-09 19:00:46 +02:00
Maurice a09d71ed12 fix(storage): a phone migration that fails to start drops the rest of the queue and says so
On the phone storage panel, when the operator accepted several category
moves and the first migration failed to start (for example the server
was busy), only that error was toasted. The other accepted moves stayed
in a hidden queue with no hint, and started on their own later whenever
the panel state happened to refresh. The desktop drops the rest of the
queue, names it in a storage.migrate.queueDropped toast and refreshes
the state, so the operator sees what did not move and saves again.

Cause: useStoragePanel only dropped the queue when the
dropQueueOnFailure option was set, and only the desktop set it.

Fix: the option is gone and useStoragePanel always drops the rest of
the queue on a failed start, toasts it and refreshes, for both shells.

Red test: FE-ADMIN-STOR-PANEL-011 (useStoragePanel.test.ts) failed with
"AssertionError: expected "vi.fn()" to be called 2 times, but got 1
times".

Changed assertions in src/components/Admin/storage/useStoragePanel.test.ts:
- FE-ADMIN-STOR-PANEL-011 (renamed from "by default a failed migration
  start only toasts and keeps the rest queued"): toast.error was called
  once, now twice; migrationQueue had length 1, now 0; refreshState was
  not called, now it is.
- FE-ADMIN-STOR-PANEL-012: no assertion changed; it no longer passes the
  removed dropQueueOnFailure option.
2026-10-09 19:00:46 +02:00
Maurice abbd2a94ce fix(admin): a failed "Load more" in the desktop release history keeps the list and can be retried
On the desktop admin GitHub panel, when "Load more" failed (for example
GitHub rate limiting), the whole release history was replaced by the
error card for the rest of the session: no list, no Load more button,
no way to retry. Had the user found a way to load again, the next load
would also have skipped the page that failed.

Cause: in its one page per load mode useGithubReleases advanced the
page counter whether the request worked or not and never cleared the
error, and GitHubPanel showed the error card whenever an error was set.
The phone (fillPages mode) already held the page, cleared the error on
the next good load and drew the error inline under its timeline.

Fix: the one page mode now moves the page on only after a good load and
clears the error then, like fillPages. GitHubPanel shows the error card
only when nothing has loaded yet; after that it keeps the timeline,
shows the error as a line above Load more and leaves the button there.
The page walk difference behind fillPages stays as it was.

Red tests:
- FE-COMP-GHREL-HOOK-012 (new, useGithubReleases.test.ts) failed with
  "AssertionError: expected [ 1, 2, 3 ] to deeply equal [ 1, 2, 2 ]".
- FE-ADMIN-GH-017 (new, GitHubPanel.test.tsx) failed with "Unable to
  find an element with the text: /v1\.\d\.0/" after the failed load.

Changed assertions in src/components/Admin/useGithubReleases.test.ts:
- FE-COMP-GHREL-HOOK-004 (renamed from "sets the error and still moves
  the page on" to "sets the error and holds the page"): requested pages
  were [1, 2, 3], now [1, 2, 2].
2026-10-09 19:00:46 +02:00
Maurice e0fd2c296b refactor(admin): draw the photo provider marks from one icons file
The desktop and the phone addon manager each carried their own copy of the Immich and Synology SVGs. Both now import them from components/Admin/addons/PhotoProviderIcons, which has its own test. The rendered marks are unchanged.
2026-10-09 16:29:40 +02:00
Maurice 05394644b9 refactor(admin): share the default user settings logic between desktop and phone
Loading, saving and resetting the defaults for users and the map field handling live in one useDefaultUserSettings hook that reads the presets and style rules from mapSettingsModel. Each shell passes its own error text and preview stamp.
2026-10-09 15:19:48 +02:00
Maurice 6c5ab5414a refactor(admin): share the JWT rotation and passkey reset between desktop and phone
useAdminUserActions now rotates the JWT secret and resets a user's passkeys for the desktop dialogs and the phone sheets. Each shell keeps its own confirm step, and the phone sheet still stays busy until the reset settles.
2026-10-09 15:19:47 +02:00
Maurice feb99ce398 refactor(admin): share the admin settings saves and the TREK API lists between desktop and phone
useAdminSettingsActions now holds the OIDC and file type saves and the five place switches, writing through the admin bag both shells already receive. trekApiModel keeps the TREK Places API fields, facts and sources the desktop card and the phone block list.
2026-10-09 15:19:46 +02:00
Maurice 7738334fd6 refactor(admin): share the addon manager and the AI parsing config between desktop and phone
useAddonManager now loads and toggles the addons and builds the provider shelves, useLlmParsingConfig holds the AI parsing form with the Ollama model list and pull, and addonModel keeps the shared types, collab sub features, labels and recommended models. Both shells keep their own markup.
2026-10-09 15:19:46 +02:00
Maurice 163b4c54e0 refactor(admin): share the notification settings saves and tests between desktop and phone
useAdminNotificationSettings now holds the SMTP save and test, the trip reminder switch and the admin webhook and ntfy save, test and token clear. The desktop tab drops its inline handlers and both views keep their markup.
2026-10-09 15:19:46 +02:00
Maurice ab3a69e18c refactor(admin): share the admin notification matrix between desktop and phone
useAdminNotificationMatrix now loads the matrix, picks the visible built in channels and saves each toggle with a per cell revert. The desktop panel and the phone card keep their own markup.
2026-10-09 15:19:46 +02:00
Maurice 78fa42a0e6 refactor(admin): share the backup panel logic between desktop and phone
useBackupAdmin now loads the backups and the auto backup schedule and runs create, download, delete, restore and the schedule save. The phone keeps its form when the server answers without settings through the keepSettingsWhenMissing option.
2026-10-09 15:19:45 +02:00
Maurice f1719c3e35 refactor(admin): share the packing template manager logic between desktop and phone
usePackingTemplates now owns the template list, the expanded template with its categories and items, and every create, rename and delete. The phone keeps its generic delete error toasts through the genericDeleteErrors option.
2026-10-09 15:19:45 +02:00
Maurice 7da3a1724c refactor(admin): share the MCP token and OAuth session admin between desktop and phone
useMcpTokensAdmin now loads both lists, revokes sessions, deletes tokens and keeps the expanded scope chips. Both panels keep their own cards and confirm dialogs.
2026-10-09 15:19:45 +02:00
Maurice 7c006b6445 refactor(admin): share the category manager logic between desktop and phone
useCategoryAdmin now holds the category list, the create and edit form, the colour presets and the delete. The phone sheet still shows a busy delete and closes itself on success through the trackDelete option.
2026-10-09 15:19:45 +02:00
Maurice 0de89b86fa refactor(admin): share the permission levels editor between desktop and phone
Loading, editing, resetting and saving the permission levels now live in one usePermissionsAdmin hook next to the category and level tables. Both panels keep their own markup.
2026-10-09 15:19:45 +02:00
Maurice a52179f685 refactor(admin): share the audit log loading and paging between desktop and phone
Both audit log panels now read one useAuditLog hook for the first page, refresh, load more and the row labels. The desktop table and the phone cards keep their own markup.
2026-10-09 15:19:45 +02:00
Maurice 40fa8c6fe6 refactor(plugins): share the plugin admin logic between desktop and phone
Both panels carried the same types, helpers and a four hundred line block of state and handlers. pluginModel now holds the types and pure helpers, usePluginsAdmin the state, loading and every action, and usePluginDetail the registry detail fetch. The phone keeps starting all updates at once from Update all and reads the detail manifest under its stricter shape; both panels keep their own markup, dialogs and sheets.
2026-10-09 15:19:45 +02:00
Maurice a99b0c5b32 refactor(storage): share the storage panel and backend editor logic between desktop and phone
useStoragePanel holds the editor, removal and sync prompts, the save with its migrate prompt and the sequential migration queue; useBackendForm holds the backend draft, its checks and the backend it builds. The desktop keeps dropping the rest of the queue when a migration fails to start, and keeps recomputing the candidates at confirm time; the phone keeps moving the candidates its prompt opened with.
2026-10-09 15:19:45 +02:00
Maurice e92b233c3a fix(admin): the phone release history no longer skips a page after a long prerelease run
When five full pages in a row held only prereleases, the walk stopped with its page counter already one past the last page it read, so the next Load more started a page later and that page of releases never showed. The counter now ends on the last page read.
2026-10-09 15:19:44 +02:00