The labels tried the cover photo and lost: a blue chip on a blue sky is simply not
there, whatever shadow it carries. They sit beside the rating now, both as badges
on one row — two answers about the place, side by side, each legible.
The note takes the full width on a ground of its own. Loose in the column it read
as another paragraph of the panel rather than as something somebody wrote about
this place.
Asked for in the feedback: a wall of round photos, and no way to tell which is
which. "Name and displaying any custom rating would be very beneficial to quickly
seeing what is around you that is of interest."
The collections map had hover switched off. The trip map has told you what a
marker is all along, so it is the same card, now shared instead of written a
third time — it already existed twice, once per renderer, and the two had drifted
in how they resolved the category icon.
The card gains a rating, and so does the marker itself: an amber disc on the
corner where a trip's day numbers sit. The two never collide, because a numbered
stop is one already planned into a day and this answers the question asked before
that. Nothing is drawn for an unrated place, which is most of them.
Also from this round: the place panel's labels move up beside the name, where a
label belongs — read three rows down they looked like a property of the rating
above them — and the rating gets a ground of its own so a line of stars stops
reading as another row of text.
Getting directions sat up by the address, in a pill of its own, while editing and
copying sat in the footer as square buttons. All three are things you do WITH the
place, so they belong together and should look like one set. Navigation moves
down beside them, and the footer takes the shape the navigation button already
had.
Book a night to check in at ten, pin a stop to three in the afternoon behind it,
and the rail said 13:46. True enough as arithmetic — with no hour of its own the
chain works the first stop backwards out of the first one that has one, so three
minus a fourteen minute drive minus an hour standing there — and nothing anybody
asked for.
A door that opens at ten, with nothing ahead of it deciding otherwise, IS the
hour: the day starts there and runs forwards. Reached later it stays what it was,
a floor, and a floor below the arrival changes nothing — which is what keeps a
hotel behind an afternoon stop at its real arrival instead of dragging it back to
its check-in.
Pinning an afternoon stop sorted that one and left the hotel it was booked around
sitting behind it. The day's auto-sort reads each stop's own hour, and a booked
night has none: nobody types a time into a hotel row, they type a check-in, and
that lives on the booking. It counts now, so the night moves with everything else
instead of staying where it was dropped.
The assignments e2e builds its own tables and had neither the accommodation
column nor day_accommodations, so every create in that file 500'd as soon as the
insert learned to carry a booking id. Both are there now.
And the shared lint, which is stricter than the other two and which I had not run:
three leftovers from taking the check-out out of the chain, none of them used any
more.
The stop went to the end of the day, always, on the reasoning that a night ends
the day. It usually does — but a check-in at eleven says the traveller is at the
desk before whatever they pinned to the afternoon, and putting the stop behind it
built a drive that reaches the hotel at seventeen past five after a sixteen
hundred it was booked around. The order looked like the traveller's and was not;
it was this code's default.
A booking with a check-in now takes the place its hour asks for: ahead of the
first stop pinned later, and still last when nothing is. Editing the check-in
re-seats it, in both directions, read off the chain rather than recomputed —
the index a fresh insert would get is not the index a row already holds.
Stops without an hour of their own are passed over in either direction. Their
place in the chain came from the traveller, not from a clock, and a night has no
business pushing past one.
The container refused to boot: Nest will not export a provider that belongs to an
imported module, and the service moved into the domain module in the commit
before. Re-exporting that module gives every importer the same service it always
got, and the wiring test now asserts the shape that actually boots rather than
the one that only typechecks.
Neither tsc nor the unit tests could see this — the module metadata is only read
when a container is built. The e2e harness does build one, and that is what
catches it.
Opening a place sent it from the bottom left of the map to the top, which reads
as the control moving house rather than as room being made. Nothing was in its
way: the inspector is a card centred between the sidebars and at most 800 wide,
so the corner it was clearing is one the card never reaches.
Both renderers, since the switch is not supposed to move when an instance changes
its map.
A stay whose place was deleted kept its row with place_id NULL: still drawn in the
day header, still naming a hotel through its partner booking, and pointing
nowhere. Nothing could reach it any more and nothing cleaned it up.
It goes with the place now, through the accommodations domain rather than a
DELETE in the places path, because a stay takes its partner booking, that
booking's expense and the day stop it wrote with it — none of which is the places
domain's business to know. Both delete paths, single and bulk.
The service moves into an AccommodationsDomainModule, the assignments-domain
precedent: the service never touches PlacesService, only the MCP tool that writes
a place and a stay together does, so it can sit in a module PlacesModule is free
to import without closing a loop. AccommodationsModule keeps exporting it, so
every existing consumer is unchanged.
It is the hour a room becomes available. Reaching it later is reaching it; only a
time somebody pinned to a stop can be missed. Read as an anchor it did the exact
opposite of what it means: arrive at 16:14 at a hotel that checks in from 11:00
and the rail reported five hours fourteen late, then read that 11:00 as earlier
than the 15:00 before it, took it for a new day and pushed the hotel onto
tomorrow. The day it came from was left with one stop and dropped out of the list
altogether, so the stop before it appeared to vanish.
The schedule now takes it as a floor: waited for when the drive gets there first,
and otherwise silent. Waiting still prints in ink, because the hour is one
somebody named; arriving late to it prints as computed, because nothing was
decided.
Same shape of mistake as the check-out, from the other side. One says the earliest
you may be somewhere, the other the latest you may still be — neither says when
you drive.
A migration runs while the old container is still answering. A booking written in
those seconds comes from code that knows nothing about the day stop and lands
behind the sweep that would have given it one: on the test instance exactly one
of ten, and invisible to the drive ever after, with no way back short of saving
it again. The sweep runs a second time, which is safe because the same NOT EXISTS
decides it both times.
The other way into that state is closed too. "Remove from day" in the place panel
took the stop and left the booking, which is the very state this whole change
exists to remove — and it was the only control that could produce it after the
upgrade. A stop a booking wrote is no longer offered there, in either direction:
removing it strands the booking, adding one puts the same hotel on the day twice.
The night is removed where it is made, in the day's overnight block or by turning
it back into a pause in road trip mode.
With daily travel times set, a stay was spent only against the hours the window
is open. Twenty-four hours then took two of them to run down: a single night
ended on the day after next, and every stop behind it moved with it.
Night time is time. The hours between one window closing and the next opening now
count towards the stay, so a night that starts just after eight is over just after
eight the next morning and the day carries on from there. An eleven-hour visit
from eight is over at seven in the evening rather than saving its last hour for
tomorrow — the traveller is still there after the driving stops, they simply do
not drive on until the window opens again.
Three things the day boundary was swallowing, all from the same stay.
The stop after it stayed on yesterday. It is reached a full day later but at a
later reading on the clock, and the only rule watching for a new day was "the
clock went backwards" — which that never does. The schedule's own count knows,
as long as it is read as a difference: a day whose first arrivals came out
negative is lifted whole, and reading the lifted offsets raw once moved an early
stop to tomorrow while leaving the late one behind. It now crosses over properly,
marked as having set off the day before.
The morning after started at the exact minute the stay ended, as if the road to
the first stop took no time. The carried minute is a departure; the drive from
there is added to it.
And that drive was invisible. It was on the map and in the day's total, but the
rail jumped straight to a stop at 08:11 after a stay that ended at 08:00. It gets
the same band any other drive gets, above the stop it arrives at — except where a
stop crossed over, which already draws that road under its own block.
A check-out is the LATEST the room has to be handed back, never the earliest
anybody may drive on, so it says nothing the chain can use. It shaped four things
it had no business shaping: the departure the day was built from, the length the
rail printed for a booked night, a day-selector and a clock field in the stop
dialog, and the branch that sent a night's STAY badge to the booking form because
its length was not editable here. All four go. The booking keeps its check-out;
it is a booking detail and lives under Days, where it is entered.
What replaces it is the thing that was always the honest answer: how long you
stand there. That is the traveller's own statement rather than a deadline, and a
stay running past midnight now carries the clock into the morning after — a stop
pinned before it ends keeps its hour and collects the late finding, as it would
from any leg it cannot make. A stay spanning whole days walks the clock forward
over them. An ordinary evening hands on nothing: the night between two days is
not a wait somebody is serving out.
The stay dialog goes to a full day rather than four hours, since it is now the
only place a night is given its hours, and offers eight and twelve as presets.
Every stop answers the same question the same way.
Overlay panels lose the skeleton placeholder. It was drawn in the page flow, so
the first time each dialog was ever opened a pale block flashed under the planner
and never again once its chunk was cached.
This reverts commit d4677bdea, and keeps the departure chip's icon.
The premise was wrong. A check-out is the LATEST moment the room has to be
handed back, not the earliest one you may leave: whoever wants to set off at
seven checks out before that or leaves the bags at the desk. Reading it as a
floor turned a perfectly good plan into a finding, and no amount of wording
around that would have made it true.
The chip keeps the door-out mark. That part says what it always said, only in a
symbol that is not a second clock face beside the arrival.
Every day was scheduled from nothing, so a booking that runs into the next day
ended at midnight as far as the chain was concerned. Check in at 08:00 and out
at 08:00 the next day, and the rail was happy to put you somewhere else at 07:00
that morning, with no sign that the room was still yours.
The day a check-out falls in now starts at the check-out rather than at nothing.
A floor, never a move: a stop with a clock of its own keeps it and collects the
late finding any leg it cannot make in time would give it, so a plan that says
07:00 still says 07:00 and merely admits it does not add up. A stop with no clock
starts at the check-out instead of having no time at all.
Only that one day. A stay running Monday to Thursday leaves Tuesday and Wednesday
alone, because you sleep there, you do not sit there.
The departure chip takes the door-out mark instead of a clock face. Beside an
arrival time, a second clock read as a second arrival.
The rail prints an hour in ink when a person picked it and in grey when the chain
worked it out. Every scheduler anchors on `time ?? checkInTime`, so a booked night
starts its day on the check-in exactly the way a pinned stop does — but two of the
three then asked only about `time` when labelling the result, and printed that
chosen hour grey.
The answer lived in three places and two of them disagreed with the anchor they
had just used, so it is one function now. Nothing but the rail reads the flag.
Only visible since a booked night became a stop of its own; before that no stop
carried a check-in.
The filter sat on the planner hook's assignments, but the day sidebar is handed
storedAssignments straight from the store, so it never saw it. The hotel was
listed as an ordinary place right under the overnight block naming it, on every
day someone booked a night for.
Filtered in getDayAssignments now, next to the service-stop filter that is there
for the neighbouring reason. That is the list itself, so the reorder and undo
paths keep reading the full day and nothing shifts underneath them. The phone
was never affected: its timeline reads the hook's copy.
The row shapers name every column they pass on, and accommodation_id was in
none of them. So the field the day list hides on never reached the client, and
the booked night was listed as an ordinary place next to the overnight block
that already named it. The stop was correct in the table the whole time; the
three read paths just dropped it on the way out.
The stamp on the freshly written stop had the same hole from the other side: the
INSERT went in without the booking id and an UPDATE added it afterwards, so the
row handed back in the answer, the one the client that booked the night puts
straight into its store, carried none. It goes in with the INSERT now.
The shared link gains the same filter. It has no road trip view to need the stop
and already shows the booking as its own chip on the day, so the row was only
ever the same hotel twice.
The booking form wrote the stay row itself, so it was the one door that left the
day stop out. A hotel entered there stayed invisible to road trip mode, which is
the same complaint as before with a different way in: enter the place twice or it
is not on the route.
It writes the stop now, through AccommodationsService rather than a second copy of
its SQL. The permission argument for not doing so does not hold: the night already
shows in the day header the moment the booking is saved, and the stop is that same
night in the other view, not a second edit to the plan.
Two holes go with it. Moving the booking left the stop on a day nobody sleeps there
any more, and deleting it left the stop behind entirely — in both cases still
carrying the booking's id, which is what the day list hides on, so nobody could
reach it to remove it. The stop now follows the stay and goes with it, and only
once the trip_id guard has confirmed the stay was this trip's to delete.
Six lines and four functions the mirror brought in were never entered by a test,
which is what took the domain under its coverage floor.
The keepStop branch of a delete, the plugin surface's own update, and the
transaction that has to roll a place and its stay back together. Plus every
sender a surface hands announceMirror: REST and the three plugin methods build
one each, and a fixture that swallowed the call let all four go unexercised --
the one link between a stay write and the sockets everyone else is watching.
Three things the first pass got wrong.
A stop a booking puts on the day is for road trip mode, which needs the
hotel in the driving chain. The day planner already shows that booking as
its own overnight block, so the row there would be the same hotel twice.
It is hidden in Days whatever the service-stop switch says, and the switch
goes back to meaning what it says: the petrol stations the traveller added.
Cancelling a night from the road trip popup took the stop with it. That
control means "this is a pause now, not a night", and the stop sits
mid-drive where re-adding it would land it at the end of the day. The
booking now hands the stop over instead of deleting it.
Moving a booking whose stop belongs to the traveller put a second one on
the new day and left the first behind, which is the duplicate this change
exists to remove. Only a booking's own stop moves now. Stays booked before
this shipped get theirs from the migration, not from the next edit they
happen to receive.
The day's accommodation is drawn as a leg to the first and from the last
waypoint. Now that a booked night puts its hotel on the check-in day as a stop
of its own, that last waypoint IS the hotel on most days, and the leg drawn to
it is a round trip of zero kilometres: in the map line, in the sidebar's leg
list and in every exported directions link, which carried the hotel twice in a
row. The defect already existed for a night booked in the road trip; the change
above would have made it the normal case.
Both bookend rules now decline a leg whose edge stop sits at the hotel's own
coordinates, which covers the map, the sidebar, the phone route and both export
links from one place. Compared by coordinates rather than ids, because the
bookend comes off the stay row and the waypoint off the assignment.
The server announces the day stop over the socket like any other assignment,
but the socket deliberately skips the session that sent the request, so the one
person who could not see their new stop was the one who had just booked the
night. That looks exactly like the bug it fixes.
The stay writes hand their answer to one applier, which folds the stop into the
store through the same path the socket uses, so the IndexedDB write-through
happens either way and the dedupe by assignment id comes for free. Three
surfaces call it: the day-detail picker, its edit branch and the phone sheet.
The road-trip flow does not, because it writes its own assignment before the
stay and there is never a mirrored stop to apply.
A road-trip stop is a day assignment: the rail, the server-side plan and the
map all build their stops from day_assignments and only look the stay up
afterwards, to hang check-in and check-out on one. A stay written without an
assignment was therefore invisible to every routing surface, so a hotel entered
in the day planner never reached the road-trip view and had to be entered a
second time as an ordinary place. The road-trip side has always written both
halves in one go; this is the day planner catching up, in the domain that
writes the stay.
Booking a night now also assigns its place to the check-in day, inside the
transaction that already writes the stay and its partner hotel reservation, and
types the place as a hotel stop when it carries no type of its own, so it reads
exactly like a night booked in the road trip. Only the check-in day gets a
stop; the later nights ride on the stay row. A place already planned for that
day gets no second stop and the booking claims none, so cancelling it cannot
delete a stop somebody placed by hand. Moving the booking moves its own stop,
deleting it takes its own stop back, and a trip re-date carries it along with
the stay. All four write surfaces (REST, plugin RPC and both MCP tools) run the
same write and announce the same events; the answers carry the stop too,
because the broadcast skips the socket that sent the request.
New nullable day_assignments.accommodation_id marks a stop as a booking's own.
It stays NULL for every existing row on purpose: everything planned before this
was planned by hand, and no backfill rewrites a finished trip. A stay booked
earlier picks up its stop the next time somebody saves it.
The booking form keeps writing stays on its own: that surface is gated on
reservation_edit and the day stop is day_edit work.
Three cases fired their reads through `Promise.all`. The Nest app under
test is never told to listen, so supertest binds an ephemeral port on the
first request; four of those at once race four `listen(0)` calls on the
same server and the losers come back as ECONNRESET. It held here and
failed on a loaded runner. The reads run one after the other now, which
is all they ever needed.
What it reads, what it never writes, what happens to the recorded
positions, and how the connection, the suggestions, the Atlas marks and
the wish list behave from the reader's side.
The trip planner and the journal get a "From Dawarich" list: the stays
recorded for those dates, each one a row that can be dismissed or turned
into a place or an entry after a review step that shows time, name, note
and day. A place that came from a recording carries the Dawarich mark
next to its name so its origin stays visible afterwards.
The recorded route lies over the trip and journal maps as an overlay,
coloured per day and cut along the reader's own timezone rather than
UTC. It is fetched for the window on screen and never stored.
The Atlas gets its own panel beside the map for the countries and cities
the recordings prove, and the wish list is ticked off there, on the day
of the visit, undoably. The phone has its own take on all of it, in the
mobile shell's colours and with native sheets.
Three small things noticed while working nearby, none of them Dawarich:
The map's layer switcher showed the browser's own tooltip while every
control beside it showed TREK's, so hovering along the row produced two
different boxes in two different styles.
The journey entry editor mixed TREK's date picker with a native time
input, and the date, which is the longer of the two, had the narrower
column: "12. Sept. 2026" wrapped while the clock sat half empty. The row
is split in the date's favour now and the picker is disabled together
with the rest of the form instead of staying live behind it.
On the phone's journey, the dock's "+" now turns into an upload button
while the Gallery tab is open, which is what that tab's one big action
actually is.
A new `integrations/dawarich` domain behind its own addon. Each reader
connects their own instance; the API key is encrypted at rest and never
returned. Nothing is written back to Dawarich, and no raw position is
kept in TREK: the recorded route is fetched per request for the window
being looked at and thrown away again.
What it produces are suggestions, never facts. A recorded stay becomes a
proposed place or journal entry, a country or city becomes a proposed
Atlas mark, and a stay near a wish becomes a proposed tick-off; each one
waits for the reader to confirm it. Identity is (user, Dawarich visit),
so a second sync neither duplicates nor overwrites what was accepted,
and deleting the result puts the suggestion back on the list.
The sync runs as a job on the central scheduler, one user at a time,
guarded against overlapping runs.
A key could only ever read everything a user owns. When a second service
reads TREK on that user's behalf, that is more than it needs: Dawarich
wants trips, days and places, not notes, bookings or the wish list.
Keys can now be minted with an explicit list of areas, checked in the
guard and shown when the key is listed. Keys that existed before this
keep full read access, so nothing that works today stops working. A key
marked as limited whose list cannot be read grants nothing rather than
everything.
Zod schemas for the Dawarich integration: connection settings, the
suggestion list and its accept payloads, the recorded-track window and
the Atlas country/city proposals. Places gain an optional `source` so a
place that came out of a recording can say so.
The tab was built to its own pattern: a bold title where the other four
carry a small uppercase one, a counter instead of the add button, the add
form as a light block sitting on top of the list, and a grey line of text
where every other empty panel shows the mascot. Side by side with Notes it
read as a different app.
It now uses the poll panel's shell: the same header, the same pill button
top right, the same modal for the form, and the mascot holding a chain.
The load effect depended on the toast and translation objects, both of
which are new on every render, so the list refetched constantly and a link
that had just been added was dropped again by the response in flight. It
depends on the trip alone now, and reads the other two through refs.
The two decorative circles in the header are positioned outside it on purpose
(-40 to -60 on each axis) and nothing in the ancestor chain clips them, so the
first one widened the document by its own overhang: a 390px phone reported a
450px scrollWidth and the whole page could be dragged sideways (#2345).
overflow: hidden on the header is what the decoration always meant.
The share page drew every stop as its own pin while the planner drew the same
trip as a handful of clusters, so a shared link opened on three dozen
overlapping markers. On a 300px-tall map that covers a whole city, the pins
underneath can be neither read nor tapped (#2343).
The option block and the icon factory move to components/Map/markerCluster.ts,
which both maps import, so the two cannot drift apart again. The planner's
options are unchanged.
leaflet.markercluster's two stylesheets were imported by MapView.tsx alone.
The share page never mounts MapView, so it imports them itself: without them
the bubbles keep the library's default styling and spiderfy's legs are
invisible.
A bare amap.com/place page carries an id and nothing else, and turning that
id into a location would need a keyed detail lookup the URL resolver has no
user for. It answers 400, which AMAP-084 pins. The wiki offered the link as
paste-able and the parseAmapUrl comment still promised the lookup, so both
now say what the code does.
The provider header also claimed photoRefs/photoBytes were deliberately
left out, naming two methods the places-provider interface no longer has.
The reason is unchanged: an Amap picture comes without a licence statement
we could show, so an Amap place gets its picture from Wikimedia.
With Amap in the keyed slot every reverse lookup went to Amap first,
worldwide. Outside its box that buys a round trip and an empty answer
before Nominatim is asked anyway, which is latency on every right click in
Lisbon. The client side of the same question already filters by the box
(placeNavigation.ts); isOutsideChina is the shared helper both use.
The two fixtures in the file that were cast to any now carry a type: the
key row and the fetch spy whose calls the reverse case reads back.
The place search provider block now renders on a managed install too, and
there the key fields are empty by design: the operator owns the credentials
and user-profile never sends them. An operator key set through the
environment is invisible in the fields either way. The warning behind the
provider select read those fields, so it told every managed admin that
search "falls back to OpenStreetMap" while their key was answering.
app-config already carries the answer as has_maps_key, and it resolves the
same chain the search itself uses, environment included. has_amap_key sits
beside it, so both come into the store at boot and both follow a save.
The index and OpenStreetMap answer every search; the provider choice
only decides who fills the keyed slot next to them. Rows carry a source
mark, a place keeps its Amap id, and the journey map shares the shifted
projection.
The API keys card was hidden wholesale on a managed install, which took
the provider choice with it although the server leaves places_provider
to the admin there. The card stays; the keys, their Save button and the
Google switches fold away, on the phone shell now too. The mobile
warning uses the pending token instead of an undefined one.
The desktop tests find a key input through its labelled toggle rather
than by position, which is where the accessible name sits.
Without a Workbox rule for is.autonavi.com the offline pre-download
fetched the tiles and stored none of them, the hole #2180 closed for the
OpenStreetMap DE and Stadia presets.
The journey map read the same tile setting as the planner but built its
Leaflet map without the shifted CRS, so an Amap basemap put every entry a
few hundred metres off. A change of datum rebuilds the map, since Leaflet
cannot swap a CRS after construction; every other template change still
retiles in place.
An Amap row now carries the same source mark the TREK index, OpenStreetMap
and Google rows get, named per locale because 高德地图 is the name it goes
by where it matters. The "search is on OpenStreetMap" notices and the
key hint in the details column are gone with the keys dev had already
removed, and the client-side provider mirror with them.
amap_poi_id travels with a picked result into the form, the details
column and the shadow log, so a saved place keeps the id it was found
by. A pasted Amap link goes to the resolver like a Google one.
The provider produced amap_poi_id, but nothing stored it, so a saved
Amap place could never be reopened against Amap. The column now travels
with google_place_id and osm_id through the shared contract, the place
insert and update, duplicate matching, the assignment projections, the
trip copy and the three place MCP tools.