mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
ed5769fc89
Attribution first, because this branch argues the point itself: the card names the sources with the comment that naming them is a licence obligation rather than decoration, and the copy beside it promises that every field says where it came from. The POI path then stamped Overture rows as OpenStreetMap. Overture is not OpenStreetMap — it carries OSM among other sources under other licences — so that is a wrong attribution rather than a rounded one. The wire contract keeps `source` an open string and nothing on the client switches on it, so saying `trek-places` costs nothing. The MCP tool description no longer calls itself an OpenStreetMap tool either, and says which source answered. Adding one place cost two round trips to the index. The details lookup fetches the place and its answer carries the description; toPlaceRecord dropped that field, so the enrichment fetched the same place again a moment later to read it. It is carried through now and read from what the caller already has, with the second lookup left as the fallback for when there is nothing to read. That description's source URL becomes an href on the client and was taken on `typeof === 'string'` alone. It now goes through the same allow-list a place's website goes through — the file already does this for menu_url, with the comment explaining why — so anything but http(s) loses the link and keeps the text. The instance token was hashed from raw APP_URL, which the shipped compose file leaves commented out. Every default install therefore sent the same token, and a token every caller shares is not a caller: one instance running a large import would rate-limit all the others, who would fall back to Nominatim with nothing to point at. getAppUrl() falls through to ALLOWED_ORIGINS and then to the port, so unconfigured instances still differ. Two client-side leaks. A pick made out of the offline cache was recorded into the shadow corpus, where it moved the two figures the corpus exists to measure — and offline is a setting, so the network is usually reachable and the row landed. And the prefetch never re-checked the session after its request came back: a logout in that window repoints the handle at the anonymous database, so one person's trip area was written where the next person's offline search reads. The cache path also ignored the abort signal, so an answer for a keystroke two keystrokes old could overwrite a newer suggestion list. It rejects the way both surfaces already know to swallow. Left alone, and said in a comment rather than hidden: the index cannot localise. The Overpass path picks name:<lang> and falls back to int_name; the service has no language parameter at all, so a German reader exploring Tokyo gets the Japanese primary names on this path.
52 lines
2.2 KiB
TypeScript
52 lines
2.2 KiB
TypeScript
import type { PlaceShadowPickRequest } from '@trek/shared'
|
|
import { apiClient } from './client'
|
|
import { useAuthStore } from '../store/authStore'
|
|
|
|
/**
|
|
* Records which search result the user picked, for the local shadow corpus.
|
|
*
|
|
* Fire and forget in the strongest sense: it never throws, never blocks the
|
|
* pick it is describing, and does nothing at all unless the instance switched
|
|
* the log on. Adding a place must not get slower or more fragile because
|
|
* somebody is collecting a measurement.
|
|
*
|
|
* The store flag is checked first so a switched-off instance makes no request
|
|
* at all; the server checks it again, because a client is not a permission.
|
|
*/
|
|
|
|
/** About 100 m. Enough to reproduce a ranking, too coarse to place someone. */
|
|
function coarse(value: number): number {
|
|
return Math.round(value * 1000) / 1000
|
|
}
|
|
|
|
export function recordPlacePick(pick: PlaceShadowPickRequest): void {
|
|
if (!useAuthStore.getState().placeShadowEnabled) return
|
|
if (!Number.isFinite(pick.pickedLat) || !Number.isFinite(pick.pickedLng)) return
|
|
if (pick.liveRank >= pick.liveCount) return
|
|
// A pick out of the offline cache describes the cache's ordering, not a
|
|
// provider's, and the two numbers this corpus exists for — how often the live
|
|
// answer was first, and how often it was in the top five — are averaged over
|
|
// every row. Mixing local rows in would move exactly the figure a candidate
|
|
// index is later judged against.
|
|
//
|
|
// Not merely an offline concern: the offline switch is a setting, so the
|
|
// network is usually reachable and the POST would go through.
|
|
if (pick.source.endsWith('offline-cache')) return
|
|
|
|
const body: PlaceShadowPickRequest = {
|
|
...pick,
|
|
query: pick.query.trim().slice(0, 200),
|
|
pickedName: pick.pickedName.slice(0, 300),
|
|
pickedLat: coarse(pick.pickedLat),
|
|
pickedLng: coarse(pick.pickedLng),
|
|
biasLat: pick.biasLat === undefined ? undefined : coarse(pick.biasLat),
|
|
biasLng: pick.biasLng === undefined ? undefined : coarse(pick.biasLng),
|
|
}
|
|
// The contract requires both; an unnamed result is not worth a row anyway.
|
|
if (!body.query || !body.pickedName) return
|
|
|
|
void apiClient.post('/place-shadow/pick', body).catch(() => {
|
|
// Deliberately silent. The user was adding a place, not running a study.
|
|
})
|
|
}
|