@autobusal/providers 1.41.4 → 1.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/types/routes.ts +16 -0
- package/types/settings.ts +9 -0
- package/CHANGELOG.md +0 -872
package/package.json
CHANGED
package/types/routes.ts
CHANGED
|
@@ -217,6 +217,22 @@ export interface PriceData {
|
|
|
217
217
|
display: string
|
|
218
218
|
offer?: boolean
|
|
219
219
|
value: number
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* What this party would have paid before a dynamic price rule reduced it.
|
|
223
|
+
*
|
|
224
|
+
* Claude - 2026-08-30 (roadmap P5, ruled by Ferjolt). Present ONLY on a
|
|
225
|
+
* reduction: "for a price rise, just the price; if the price drops, the
|
|
226
|
+
* standard fare cut through, and the actual price discounted". A surcharge
|
|
227
|
+
* deliberately publishes nothing here, so there is no "was" figure lying
|
|
228
|
+
* around for a card to render as a discount that never happened.
|
|
229
|
+
*
|
|
230
|
+
* It is a real total for the passengers being quoted - the same arithmetic
|
|
231
|
+
* as `value`, run against the pre-modifier fares - not a list price nobody
|
|
232
|
+
* was offered.
|
|
233
|
+
*/
|
|
234
|
+
base?: number
|
|
235
|
+
base_display?: string
|
|
220
236
|
from: LocationData
|
|
221
237
|
to: LocationData
|
|
222
238
|
adult: number
|
package/types/settings.ts
CHANGED
|
@@ -131,6 +131,15 @@ export interface SettingsData {
|
|
|
131
131
|
languages: {
|
|
132
132
|
available: string[]
|
|
133
133
|
default: string
|
|
134
|
+
/*
|
|
135
|
+
* Claude - 2026-08-30: every language the PLATFORM ships, as opposed to
|
|
136
|
+
* the ones this brand publishes in. The two answer different questions
|
|
137
|
+
* and a screen that confuses them gets the wrong one - `available` is
|
|
138
|
+
* what this site is served in, `all` is what we can WRITE in, which is
|
|
139
|
+
* what the operator-invitation form needs. Optional so nothing that
|
|
140
|
+
* never asks for it is affected.
|
|
141
|
+
*/
|
|
142
|
+
all?: string[]
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
social: {
|
package/CHANGELOG.md
DELETED
|
@@ -1,872 +0,0 @@
|
|
|
1
|
-
## 1.41.4 - 2026-08-29
|
|
2
|
-
|
|
3
|
-
- useGetMenu SUBSCRIBES to the language rather than only keying on it (audit A6-73). `i18next.language` was read during render and nothing re-rendered on a switch, so the i18n strings changed instantly while the admin-managed menu labels stayed in the previous language until a full reload.
|
|
4
|
-
|
|
5
|
-
- SettingsData.preferences gains `legal` (company, address, city, VAT number) for the footer, and FoundData gains `stops` - the itinerary between the searched cities, with midnight offsets.
|
|
6
|
-
|
|
7
|
-
## 1.41.3 - 2026-08-29
|
|
8
|
-
|
|
9
|
-
- UserData gains shipping_enabled (published by Auth\Export for operator/employee accounts) so menus can hide the Shipments module honestly (audit A1-37).
|
|
10
|
-
|
|
11
|
-
# Changelog
|
|
12
|
-
|
|
13
|
-
## 1.41.0 (2026-08-29)
|
|
14
|
-
|
|
15
|
-
- The app-shell title is set through document.title instead of a rendered <title> element - React 19 does not deduplicate titles across components, so the shell's and the page's both reached <head>, which is what Meta's cleanup was deleting one of (Sentry BUSMAGUS-7).
|
|
16
|
-
|
|
17
|
-
## 1.40.7 (2026-08-28)
|
|
18
|
-
|
|
19
|
-
- useReferralTouch: the web finally reads ?ref= - touch on landing, token in localStorage (referral_token field: the BFF strips top-level token); storedReferral() exported for checkout replay.
|
|
20
|
-
|
|
21
|
-
## 1.40.6 (2026-08-26)
|
|
22
|
-
|
|
23
|
-
- settings types: addons gains `flex` and `seats` entries (both optional).
|
|
24
|
-
|
|
25
|
-
## 1.40.4
|
|
26
|
-
|
|
27
|
-
### Fixed
|
|
28
|
-
|
|
29
|
-
- **The Content-Language header survives a cold load** - requests firing before i18next.init resolved carried no language at all, which is the recurring English-menu-under-/sq leak (CMS-05/CMS-07). The interceptor now falls back to the URL's locale prefix, then the stored preference.
|
|
30
|
-
|
|
31
|
-
## 1.40.3
|
|
32
|
-
|
|
33
|
-
### Fixed
|
|
34
|
-
|
|
35
|
-
- **An expired session interrupted on a private page carries that page through the logout hop** (`/account/logout?return=...`), so signing back in can resume there. (Audit ADM-08a.)
|
|
36
|
-
|
|
37
|
-
## 1.40.2
|
|
38
|
-
|
|
39
|
-
### Fixed
|
|
40
|
-
|
|
41
|
-
- **A missing blog/help/page slug showed the "system error" screen, not a
|
|
42
|
-
404.** The public content screens load through `useSuspenseQuery`, whose
|
|
43
|
-
throw the ErrorBoundary rendered as a system error for every case; a 404
|
|
44
|
-
(unknown slug) or 422 (the articles endpoint's "invalid slug" on an
|
|
45
|
-
absent/unpublished article) is now shown as the styled 404. (Audit
|
|
46
|
-
CMS-01f/CMS-03d.)
|
|
47
|
-
- **The public navigation menu was always English.** obtapi localises the
|
|
48
|
-
labels off `Content-Language` - sent correctly - but the menu was cached
|
|
49
|
-
under a language-blind key, so it loaded once and a language switch never
|
|
50
|
-
refetched it. Keyed on the active language. (Audit CMS-07b.)
|
|
51
|
-
|
|
52
|
-
## 1.37.11
|
|
53
|
-
|
|
54
|
-
### Added
|
|
55
|
-
|
|
56
|
-
- Saved-passenger types and the account query, shared by the checkout chooser and the account manager so both read one shape.
|
|
57
|
-
|
|
58
|
-
## 1.37.10
|
|
59
|
-
|
|
60
|
-
### Added
|
|
61
|
-
|
|
62
|
-
- `ReviewData.published_on` - the review's date as ISO 8601, beside the localized display string. Structured data needs a machine date, and reversing a printed `d/m/Y` back into one would couple the schema to a locale format.
|
|
63
|
-
|
|
64
|
-
## 1.37.9
|
|
65
|
-
|
|
66
|
-
### Added
|
|
67
|
-
|
|
68
|
-
- **`Errors/Message` carries a `data-error-page` marker.** The prerenderer has to tell a rendered page from a rendered not-found, and it cannot do that by matching text when the app speaks fifteen languages - it was writing 404s to disk as if they were pages and counting them as successes.
|
|
69
|
-
|
|
70
|
-
## 1.37.8
|
|
71
|
-
|
|
72
|
-
### Added
|
|
73
|
-
|
|
74
|
-
- **`SettingsData.map.tile_url`** - the basemap route maps draw on, from `/settings/get`. `null` until a commercial tile provider is configured server-side, which every client reads as "keep your built-in OpenStreetMap default". Served rather than built in because the mobile app shares this switch, and a compiled-in tile URL there could only be changed by shipping a store release.
|
|
75
|
-
|
|
76
|
-
## 1.37.7
|
|
77
|
-
|
|
78
|
-
### Added
|
|
79
|
-
|
|
80
|
-
- **`OrderData.owner_category`** - `'agency' | 'operator' | 'individual'`, who actually owns an order, collapsed to what a viewer outside the account needs to know: who to contact if it needs to change. See `Libraries\Orders\View::ownerCategory()` (obtapi).
|
|
81
|
-
|
|
82
|
-
### Changed
|
|
83
|
-
|
|
84
|
-
- **`OrderData.comission_affiliate_display` is now optional.** obtapi's `View::get()` hides every `comission_*_display` field on a restricted (public lookup) response - see that method's own `$restricted` parameter for why.
|
|
85
|
-
|
|
86
|
-
## 1.37.6
|
|
87
|
-
|
|
88
|
-
### Fixed
|
|
89
|
-
|
|
90
|
-
- **The DOM-corruption self-heal watchdog now actually recovers every navigation it's supposed to.** It had two gaps that left users stuck exactly like the bug it exists to catch: its 10-second reload cooldown was tracked globally, so a SECOND corrupted navigation to a different URL within 10s of a first one was silently swallowed - no reload, no recovery, stuck. Scoped the cooldown per-URL instead: a fresh URL always gets a fresh recovery attempt; only a repeat failure on the SAME URL is throttled (the loop-protection this cooldown exists for in the first place). It also only listened for synchronous `error` events - a promise-rejection-based failure (plausible from the lazy()/dynamic-import machinery a route transition drives) was invisible to it and got no recovery at all. Added an `unhandledrejection` listener alongside the existing one, both routed through the same recovery check.
|
|
91
|
-
- **`apiClient`'s 401 handler no longer redirects multiple times for one dead session.** A page boot fires several requests in parallel (settings, menu, account refresh, whatever the current page needs); on a stale/invalid session every one of them 401s within the same tick, and each independently ran its own async `/session` confirmation and its own `window.location.href` redirect - the existing pathname guard only protects against a request that 401s AFTER a redirect has already navigated away, not against several 401s racing each other before any of them has. Reproduced live: a stale session landed on `/account/logout`, and before that page's own logout mutation finished, a second queued redirect (from another request's 401, still in flight when the first one fired) reloaded the page again - repeatedly, stacking a fresh "logged out" toast for every concurrent 401 the boot had fired. Now guarded with a synchronous flag set before the async confirmation, so only the first 401 of a burst does the work; every other one concurrent with it short-circuits immediately.
|
|
92
|
-
|
|
93
|
-
## 1.37.5
|
|
94
|
-
|
|
95
|
-
### Fixed
|
|
96
|
-
|
|
97
|
-
- **`input[type="search"]` now gets the same theme styling as every other text-like input.** `GlobalStyles`'s selector list named `text`/`number`/`email`/`password` but not `search` - any search box on the site (the Help Center's new one) rendered with the browser's bare default appearance instead of the theme's background/border/radius/font.
|
|
98
|
-
|
|
99
|
-
## 1.37.4
|
|
100
|
-
|
|
101
|
-
### Changed
|
|
102
|
-
|
|
103
|
-
- **`CategoryData.name` dropped its dead `name_en`/`name_sq` fields** - `articles_categories.name` is JSON keyed by locale now (obtapi's categories_name_to_json migration); `CategoryData` is the resolved-string shape an article's own `category` and the admin browse list use, the admin Manage form works with the raw per-locale shape directly (see magus Admin/Categories/services.ts).
|
|
104
|
-
|
|
105
|
-
## 1.37.3
|
|
106
|
-
|
|
107
|
-
### Added
|
|
108
|
-
|
|
109
|
-
- **`SettingsData.currency`** - the label's ISO 4217 code, now exposed on the public `/api/settings/get` response. The same source `Facts` already trusted for its own `Offer` schema; every price elsewhere on the SPA is a pre-formatted display string with no machine-readable currency behind it, so this closes that gap for anywhere else that needs one.
|
|
110
|
-
|
|
111
|
-
## 1.37.2
|
|
112
|
-
|
|
113
|
-
### Added
|
|
114
|
-
|
|
115
|
-
- **`OperatorData` gains the payout bank detail fields** (`bank_name`, `bank_address`, `iban`, `swift`, `bank_currency`, `account_owner`, `bank_document_url`) - see `@autobusal/account-update` 1.2.1.
|
|
116
|
-
|
|
117
|
-
## 1.37.1
|
|
118
|
-
|
|
119
|
-
### Added
|
|
120
|
-
|
|
121
|
-
- **`MenuData.location` gains `'footer_1' | 'footer_2'`** - the site footer's two link columns are now admin-managed Menu items (same model the header nav's left/right already use) instead of hardcoded JSX. See `@autobusal/admin-menu` 1.1.3.
|
|
122
|
-
|
|
123
|
-
## 1.37.0
|
|
124
|
-
|
|
125
|
-
### Added
|
|
126
|
-
|
|
127
|
-
- **Shipments types** (`ShipmentData`, `ShipmentStatus`, `SHIPMENT_STATUSES`, `ShipmentHistoryData`, etc.) for the new operator shipping module - see `@autobusal/operator-shipments` and `@autobusal/track-shipment`.
|
|
128
|
-
- **`OperatorData.mobile_access` / `OperatorData.shipping_enabled`** - both admin-only booleans, properly typed now rather than one of them (`mobile_access`) being narrow-cast at its one call site to avoid a cross-repo publish that this release needed anyway.
|
|
129
|
-
|
|
130
|
-
## 1.36.0
|
|
131
|
-
|
|
132
|
-
### Added
|
|
133
|
-
|
|
134
|
-
- **`LabelSettings.locales`** - `{default, available}`, an admin-editable override of `preferences.languages` (which UI locales this label's site responds to, and its default). Null until the new admin-label Locales tab saves one, in which case `preferences.languages` keeps coming from the label's settings.json file exactly as before.
|
|
135
|
-
|
|
136
|
-
## 1.35.0
|
|
137
|
-
|
|
138
|
-
### Changed
|
|
139
|
-
|
|
140
|
-
- **`PageData` (the admin Pages shape) is now locale-object-based, not `_en`/`_sq` columns.** `title`/`content`/`keywords`/`description` are each `LocalizedText` (`Record<string, string>`, one value per locale) - matches obtapi's `pages_content_to_json` migration, which moved `pages` off the two-content-language ceiling onto the same JSON-per-locale shape `faqs` already established. `title_en`/`title_sq`/`content_en`/`content_sq` and friends are gone from the type entirely. New `PageSaveData` covers what the admin save endpoint actually accepts (each field JSON-stringified, not the raw object) - the public page-view response (`@autobusal/page/types.ts`) is unaffected, it was already a single resolved string per field.
|
|
141
|
-
|
|
142
|
-
## 1.34.0
|
|
143
|
-
|
|
144
|
-
### Added
|
|
145
|
-
|
|
146
|
-
- **`OperatorData.brand_name` / `OperatorData.display_name`** - an operator's optional public-facing trade name and the computed field (`brand_name` if set, else `company`) every customer-facing display should read. `company` stays as the registered legal name, for legal/financial contexts only.
|
|
147
|
-
|
|
148
|
-
## 1.33.0
|
|
149
|
-
|
|
150
|
-
### Added
|
|
151
|
-
|
|
152
|
-
- **`LabelSettings.ai`** and **`LabelData.is_main`** - a label's per-provider AI configuration (`{providers: {openai: {status, api_key?, models?, default_model?, labels_allowed?}, ...}, default_provider}`) and whether this label IS the platform's main one, both needed by the new admin-label AI tab to know which fields to show.
|
|
153
|
-
|
|
154
|
-
## 1.32.0
|
|
155
|
-
|
|
156
|
-
### Added
|
|
157
|
-
|
|
158
|
-
- **`LabelSettings.theme`** — a label's chosen theme preset (`{preset, overrides}`), matching obtapi's new `labels_settings.theme` column. Optional/nullable: absent means the label still reads its colors straight out of settings.json, exactly as before this existed.
|
|
159
|
-
|
|
160
|
-
## 1.31.1
|
|
161
|
-
|
|
162
|
-
### Fixed
|
|
163
|
-
|
|
164
|
-
- **A 422 no longer stacks one toast per query.** The search results page
|
|
165
|
-
asks three endpoints the same question, so a malformed URL rejected three
|
|
166
|
-
times and showed three identical messages. De-duplicated by the first
|
|
167
|
-
field error rather than by the message: Laravel's summary appends "(and N
|
|
168
|
-
more errors)" and each endpoint validates a different rule set, so the
|
|
169
|
-
three strings differ in that count alone while describing one problem.
|
|
170
|
-
|
|
171
|
-
## 1.31.0
|
|
172
|
-
|
|
173
|
-
### Added
|
|
174
|
-
|
|
175
|
-
- **`PopularRoutesData`** - the city pairs behind the home page's route
|
|
176
|
-
columns. `from` and `to` arrive localised and `link` is built server-side,
|
|
177
|
-
so the search URL's date and slug rules live in one place.
|
|
178
|
-
|
|
179
|
-
## 1.30.1
|
|
180
|
-
|
|
181
|
-
### Fixed
|
|
182
|
-
|
|
183
|
-
- **Only the documents 403 redirects to the documents page.** This fired on
|
|
184
|
-
every 403 there is. The intent is sound - an account blocked by an unsigned
|
|
185
|
-
document should be taken to the screen that unblocks it - but the other
|
|
186
|
-
source of a 403 in this API is the email-verification gate, and sending
|
|
187
|
-
somebody there for that told them nothing and fixed nothing. It read as a
|
|
188
|
-
deliberate product redirect and was this line. Keyed on a `reason` the
|
|
189
|
-
server sets rather than the message, which is translated fifteen ways, and
|
|
190
|
-
guarded against redirecting the documents page to itself.
|
|
191
|
-
|
|
192
|
-
## 1.30.0
|
|
193
|
-
|
|
194
|
-
### Added
|
|
195
|
-
|
|
196
|
-
- **`BootstrapError` tells a transient failure from a real one and recovers
|
|
197
|
-
by itself.** A rate limit or a dropped connection is not a system error,
|
|
198
|
-
and "something broke on our side" is wrong twice for either: it blames the
|
|
199
|
-
wrong thing, and it hands the visitor a reload button for a situation where
|
|
200
|
-
reloading immediately makes it worse. 429/503/504 and a request with no
|
|
201
|
-
response at all now get their own wording and an automatic retry that backs
|
|
202
|
-
off 5s, 10s, 20s, 30s.
|
|
203
|
-
|
|
204
|
-
## 1.29.4
|
|
205
|
-
|
|
206
|
-
### Added
|
|
207
|
-
|
|
208
|
-
- **`OrderData.addons`** (`OrderAddonData[]`) - the extra services bought at
|
|
209
|
-
checkout. There is no `data` field by design: the API withholds it because
|
|
210
|
-
the Telegram add-on's copy holds a bind token.
|
|
211
|
-
- **`OrderData.fare_display`** - the fare WITHOUT add-ons. Optional, because
|
|
212
|
-
only the single-order endpoint opts into it; `amount_display` remains fare +
|
|
213
|
-
add-ons.
|
|
214
|
-
|
|
215
|
-
## 1.29.3
|
|
216
|
-
|
|
217
|
-
### Fixed
|
|
218
|
-
|
|
219
|
-
- **A failed settings fetch white-screened the entire app.** `useGetSettings`
|
|
220
|
-
is a *suspense* query, so a failure throws during render rather than
|
|
221
|
-
resolving to an error state — and nothing in `Providers` caught it, so the
|
|
222
|
-
whole tree unmounted and left a blank page with no message, no retry and
|
|
223
|
-
nothing in the console. A rate limit, a brief API blip or a restart
|
|
224
|
-
mid-deploy all produced the same silent nothing. Reproduced in a browser,
|
|
225
|
-
not deduced.
|
|
226
|
-
|
|
227
|
-
Fixed with a new **`BootstrapError`** boundary wrapping everything. It
|
|
228
|
-
cannot be the existing `ErrorBoundary`: that renders `Errors/Message`,
|
|
229
|
-
which calls `useGetSettings()` itself and `useNavigate` — above the
|
|
230
|
-
settings fetch it would rethrow inside its own fallback. An error screen
|
|
231
|
-
for "nothing loaded" cannot depend on anything having loaded, so this one
|
|
232
|
-
uses no settings, no i18next, no router and no theme — inline styles and
|
|
233
|
-
the static `@lang/errors` map, with a reload button.
|
|
234
|
-
|
|
235
|
-
### Changed
|
|
236
|
-
|
|
237
|
-
- **Queries retry what can plausibly differ, and only that.** `retry: false`
|
|
238
|
-
was wrong in both directions: a single dropped connection or 500 was fatal
|
|
239
|
-
(and on the suspense bootstrap path, fatal meant the whole app), while
|
|
240
|
-
nothing distinguished that from a 401/403/404 — a decided answer that
|
|
241
|
-
repeats identically while spending the rate-limit budget a 429 exists to
|
|
242
|
-
protect. Now 5xx, 408, 425, 429 and network-level failures get two retries
|
|
243
|
-
at 700ms then 1400ms; everything else fails at once, so a real 403 surfaces
|
|
244
|
-
as a real 403 instead of three seconds of spinner.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
## 1.29.2
|
|
248
|
-
|
|
249
|
-
### Removed
|
|
250
|
-
|
|
251
|
-
- **Everything 1.29.0/1.29.1 added for multi-TLD** — `getLanguage`'s
|
|
252
|
-
`domainLocale` argument and the module-level origin it defaulted to, the
|
|
253
|
-
argument `Setup` passed it, and `SettingsData.domain` / `.domains` /
|
|
254
|
-
`.locale`. The stored language preference is once again the only thing that
|
|
255
|
-
outranks the brand default on an unprefixed URL.
|
|
256
|
-
|
|
257
|
-
See `@autobusal/common@1.27.1` for why. Published forward rather than
|
|
258
|
-
downgraded, for the same reason.
|
|
259
|
-
|
|
260
|
-
## 1.28.1
|
|
261
|
-
|
|
262
|
-
### Fixed
|
|
263
|
-
|
|
264
|
-
- **The stale-chunk guard budgeted two reloads instead of one.** Measured: a
|
|
265
|
-
failure at `/operators` reloaded, the server answered with its canonical
|
|
266
|
-
`/operators/`, the same chunk failed again — and the raw path no longer
|
|
267
|
-
matched the one already spent, so it reloaded a second time. Bounded, but
|
|
268
|
-
twice what was intended, and on a genuinely broken deploy the user watched
|
|
269
|
-
the page flash twice before the error appeared. The budget key now strips a
|
|
270
|
-
trailing slash, so one journey gets one reload.
|
|
271
|
-
|
|
272
|
-
Only visible because the retest ran against a chunk that was *permanently*
|
|
273
|
-
missing rather than replaced — the ordinary stale-bundle case recovers on
|
|
274
|
-
the first reload and never reaches the second.
|
|
275
|
-
|
|
276
|
-
## 1.28.0
|
|
277
|
-
|
|
278
|
-
### Added
|
|
279
|
-
|
|
280
|
-
- **`registerStaleChunkRecovery()`** — a tab left running a bundle that no
|
|
281
|
-
longer exists now reloads itself onto the page it was asked for, instead of
|
|
282
|
-
freezing.
|
|
283
|
-
|
|
284
|
-
Every route is `lazy(() => import(...))`, the build emits content-hashed
|
|
285
|
-
chunks, and it **empties the output directory** — so a new build deletes the
|
|
286
|
-
old chunk names outright (watched `index-DFPpSljG.js` disappear across one).
|
|
287
|
-
A tab still holding the previous `index.html` goes on asking for those names,
|
|
288
|
-
so the next navigation fetches a chunk that no longer resolves, the import
|
|
289
|
-
rejects, and the routed component never mounts — while the URL has *already*
|
|
290
|
-
changed. The address bar says one page and the screen shows the last one.
|
|
291
|
-
From the outside: "the page does not react at all, but the url seems to
|
|
292
|
-
change."
|
|
293
|
-
|
|
294
|
-
One listener on Vite's `vite:preloadError` covers all 141 lazy routes, which
|
|
295
|
-
is why this is a small module rather than a change to 141 call sites.
|
|
296
|
-
|
|
297
|
-
**Silent, not a prompt.** Every lazy chunk in both apps is a *route* —
|
|
298
|
-
checked, there is no lazily-loaded in-page component — so a chunk is only
|
|
299
|
-
ever fetched while navigating, which means the user has already left the page
|
|
300
|
-
they were on. Reloading to the destination costs them nothing the click was
|
|
301
|
-
not already costing them, and a "new version available?" dialog would be
|
|
302
|
-
asking permission to do the thing they just asked for.
|
|
303
|
-
|
|
304
|
-
**It cannot loop.** A missing chunk is usually a stale bundle but might be a
|
|
305
|
-
broken deploy, and reloading into a repeating failure would be far worse than
|
|
306
|
-
the bug. Each path gets at most one reload per tab; after that the error is
|
|
307
|
-
left to surface. `sessionStorage`, so the budget is per tab.
|
|
308
|
-
|
|
309
|
-
Verified by reproducing it: a tab pinned to an older bundle, its chunk
|
|
310
|
-
orphaned by a rebuild, then a client-side click — `vite:preloadError` fired,
|
|
311
|
-
the document was replaced, and the tab landed on `/operators` running the new
|
|
312
|
-
bundle.
|
|
313
|
-
|
|
314
|
-
## 1.27.0
|
|
315
|
-
|
|
316
|
-
### Added
|
|
317
|
-
|
|
318
|
-
- `LabelSettings.sms` (BulkGate: status, credentials, sender) and
|
|
319
|
-
`SettingsData.addons.sms` — the paid per-order confirmation. Both optional,
|
|
320
|
-
so an older payload cannot crash a checkout.
|
|
321
|
-
## 1.26.0
|
|
322
|
-
|
|
323
|
-
### Added
|
|
324
|
-
|
|
325
|
-
- `FoundData.arrival_offset` — whole days between boarding and arriving, for
|
|
326
|
-
the overnight marker on result cards. Optional: external-provider offers do
|
|
327
|
-
not report one, and a missing value must read as same-day rather than
|
|
328
|
-
invent a day.
|
|
329
|
-
|
|
330
|
-
## 1.25.0
|
|
331
|
-
|
|
332
|
-
### Added
|
|
333
|
-
|
|
334
|
-
- `LabelSettings.whatsapp.alert_template_name` / `alert_template_language` —
|
|
335
|
-
the separate approved template the route+date broadcast composer sends
|
|
336
|
-
through. Optional: a brand can confirm purchases over WhatsApp without ever
|
|
337
|
-
broadcasting, and Meta approves a template for a stated purpose, so service
|
|
338
|
-
alerts cannot reuse the confirmation one.
|
|
339
|
-
|
|
340
|
-
## 1.24.0
|
|
341
|
-
|
|
342
|
-
**New `Setup/commerce.ts` — the booking funnel, pushed to the dataLayer.**
|
|
343
|
-
|
|
344
|
-
`analytics.ts` already loaded the container and announced page views; this is what makes those page views mean something. Until now the funnel was unmeasurable for a more basic reason than missing events — every step of the wizard shared one URL, so no tool could tell a search from a checkout.
|
|
345
|
-
|
|
346
|
-
Uses **GA4's own event names** (`view_item_list`, `select_item`, `begin_checkout`, `add_to_cart`, `remove_from_cart`, `purchase`) so a container wires them by picking from a list rather than needing a custom mapping per brand.
|
|
347
|
-
|
|
348
|
-
`purchase` is the hard one and the reason this is a module rather than a few inline pushes: a card payment leaves the site entirely and returns on a URL carrying nothing but an order hash, and most buyers are guests with no account to read the order back from. So `stash()` remembers the value at order time and `flush()` announces it on return — clearing as it fires, and checking the hash, so neither a refresh nor a stale entry from an abandoned attempt can report a sale.
|
|
349
|
-
|
|
350
|
-
Each push clears `ecommerce` first; GTM's dataLayer is cumulative, and without that a later event inherits the previous one's items.
|
|
351
|
-
|
|
352
|
-
## 1.23.0
|
|
353
|
-
|
|
354
|
-
- `CountryData.timezone` — the IANA zone, so the date picker can floor itself on the departure city's date rather than ours or the buyer's. Carried on `/api/cities/browse`, which the search form already loads.
|
|
355
|
-
|
|
356
|
-
## 1.22.0
|
|
357
|
-
|
|
358
|
-
**Flexible Ticket, and the `policies` rename.**
|
|
359
|
-
|
|
360
|
-
- `PolicyData` is now `InformationData`, and its field `policies` is `information` — the route's free-text box is luggage, pets, boarding and ID, and never was cancellation terms. Calling it Policies invited operators to type terms into it that would then contradict the terms actually sold.
|
|
361
|
-
- `RouteData.policies_en|sq` → `information_en|sq`.
|
|
362
|
-
- `RouteData` gains `refund_hours` / `reschedule_hours`: the operator's own notice windows, in hours before departure. **Internal** — they reach the operator's route form and the admin, never a public payload. `null` means "not offered on this line", which is why they are nullable rather than 0.
|
|
363
|
-
- New `FlexOffer`, the response of `GET /api/orders/flex`. Its hours are already resolved to the **stricter** leg of a return trip, so they are the numbers that will be enforced. Either may be null on its own — an operator who will reschedule but not refund is a real case.
|
|
364
|
-
|
|
365
|
-
All notable changes to `@autobusal/providers` are documented here. This project
|
|
366
|
-
adheres to [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
367
|
-
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
368
|
-
|
|
369
|
-
> Note: 1.8.0 through 1.11.0 were published without changelog entries. The
|
|
370
|
-
> gap is left as-is rather than reconstructed after the fact.
|
|
371
|
-
|
|
372
|
-
## 1.21.0
|
|
373
|
-
|
|
374
|
-
`SettingsData.today` — the earliest bookable date as the server reckons it,
|
|
375
|
-
handed to the calendar at startup so the picker stops flooring on the buyer's
|
|
376
|
-
device clock.
|
|
377
|
-
|
|
378
|
-
## 1.20.0
|
|
379
|
-
|
|
380
|
-
`FoundData.departs_in` — minutes until departure, for same-day searches only,
|
|
381
|
-
null on every other date.
|
|
382
|
-
|
|
383
|
-
## 1.19.0
|
|
384
|
-
|
|
385
|
-
`types/reviews.ts` — `RatingData`, `ReviewData`, `ReviewSummaryData`. `rating`
|
|
386
|
-
added to `OperatorData` and `RouteData`, always optional and often absent.
|
|
387
|
-
|
|
388
|
-
## 1.18.0
|
|
389
|
-
|
|
390
|
-
`LabelSettings.reviews` — the per-brand review moderation setting
|
|
391
|
-
(`auto_publish`, `threshold`). Absent or false means every review waits for a
|
|
392
|
-
human, which is the default for every brand.
|
|
393
|
-
|
|
394
|
-
## [1.40.5] - 2026-08-26
|
|
395
|
-
|
|
396
|
-
### Added
|
|
397
|
-
|
|
398
|
-
- `OrderData.round_trip?` - whether the order is already half of a round
|
|
399
|
-
trip (either leg); computed by obtapi's single-order endpoint.
|
|
400
|
-
|
|
401
|
-
## [1.17.0] - 2026-08-02
|
|
402
|
-
|
|
403
|
-
### Changed
|
|
404
|
-
|
|
405
|
-
- **`FeatureData` gains `slug` and drops `name_sq`.** Amenity names are
|
|
406
|
-
resolved server-side from obtapi's `lang/{locale}/features.php` now, so
|
|
407
|
-
`name` already arrives in the request's language - for the SPA and the
|
|
408
|
-
partner API alike, from one resolution point. `slug` is the stable
|
|
409
|
-
machine-readable key: `id` is ours alone and `name` moves with the
|
|
410
|
-
language, so it is the only thing safe to map against.
|
|
411
|
-
|
|
412
|
-
## [1.16.0] - 2026-08-02
|
|
413
|
-
|
|
414
|
-
### Changed
|
|
415
|
-
|
|
416
|
-
- **SEO overrides are per language.** `LabelSettings.seo` and the location
|
|
417
|
-
types carry a `{locale: {title, description}}` map. The label's homepage
|
|
418
|
-
override is sent WHOLE and picked client-side, unlike the country/city
|
|
419
|
-
ones which resolve server-side - not an inconsistency but a bootstrap fact:
|
|
420
|
-
`/api/settings/get` is the one request that runs before i18n exists, so the
|
|
421
|
-
server cannot know which language to answer in. Resolving it there put
|
|
422
|
-
English copy on the Italian homepage.
|
|
423
|
-
- **`seo_keywords` is gone** everywhere. Google has ignored the meta keywords
|
|
424
|
-
tag since 2009; the field could only ever cost somebody an afternoon.
|
|
425
|
-
|
|
426
|
-
## [1.15.0] - 2026-08-02
|
|
427
|
-
|
|
428
|
-
### Changed
|
|
429
|
-
|
|
430
|
-
- **The URL decides the language.** `getLanguage()` now reads a locale prefix
|
|
431
|
-
off the path before falling back to the stored preference and then the
|
|
432
|
-
brand default, and takes the brand's `available` list so an unrecognised
|
|
433
|
-
first segment (`/city/tirana`) is never mistaken for a language. This is
|
|
434
|
-
the point of localised URLs: a link has to decide what its recipient sees,
|
|
435
|
-
so someone whose last visit was in Albanian still gets Italian from a
|
|
436
|
-
shared `/it/...` link. Unprefixed URLs are unaffected.
|
|
437
|
-
|
|
438
|
-
## [1.14.0] - 2026-08-02
|
|
439
|
-
|
|
440
|
-
### Changed
|
|
441
|
-
|
|
442
|
-
- **`LabelSettings.telegram`/`.whatsapp` drop `price` and `refundable`.** They
|
|
443
|
-
moved to obtapi's `addons` table; the label manager reads them from the
|
|
444
|
-
label's `addons` map instead (see `@autobusal/admin-label` 1.15.0). The
|
|
445
|
-
public `settings.addons` shape is unchanged, but its contents are now
|
|
446
|
-
filtered by who is asking and through what channel.
|
|
447
|
-
|
|
448
|
-
## [1.13.0] - 2026-08-01
|
|
449
|
-
|
|
450
|
-
### Removed
|
|
451
|
-
|
|
452
|
-
- **`CityData.booking_id`.** The only thing it ever fed was the Booking.com
|
|
453
|
-
affiliate banner on the results page, which is gone (see
|
|
454
|
-
`@autobusal/routes-order` 1.12.0), along with the `cities.booking_id`
|
|
455
|
-
column behind it. Type-only change; nothing read the field at runtime.
|
|
456
|
-
|
|
457
|
-
## [1.12.0] - 2026-08-01
|
|
458
|
-
|
|
459
|
-
### Added
|
|
460
|
-
|
|
461
|
-
- `FoundData.stops_count` - intermediate stops between the searched from/to,
|
|
462
|
-
where 0 means a direct trip. Optional, because external-provider offers do
|
|
463
|
-
not report one: a filter must read "missing" as unknown, never as direct.
|
|
464
|
-
- `FoundData.priority` - the operator's subscription priority, which obtapi
|
|
465
|
-
was already sending but which was not typed. Used only as a tiebreak in
|
|
466
|
-
routes-order's "recommended" ordering.
|
|
467
|
-
|
|
468
|
-
## [1.7.0] - 2026-07-31
|
|
469
|
-
|
|
470
|
-
### Added
|
|
471
|
-
|
|
472
|
-
- **`LabelSettings.seo` / `SettingsData.seo`** (`types/settings.ts`): optional
|
|
473
|
-
homepage title/description/keywords override, admin-managed via the new
|
|
474
|
-
`@autobusal/admin-label` 1.9.0 SEO tab and persisted through obtapi's
|
|
475
|
-
`Labels\AdminController::seo()`. `SettingsData.seo` is the public-safe
|
|
476
|
-
subset exposed by `Settings\ViewController::get()` for `StaticHome.tsx`
|
|
477
|
-
to prefer over its i18n translation-template values when set.
|
|
478
|
-
- **`CountryData.seo_title`/`seo_description`/`seo_keywords` and the same
|
|
479
|
-
three fields on `CityData`** (`types/locations.ts`): optional per-entity
|
|
480
|
-
override for a country/city landing page's meta tags, admin-managed
|
|
481
|
-
through the existing Countries/Cities admin forms and persisted via
|
|
482
|
-
obtapi's `Countries\AdminController`/`Cities\AdminController`.
|
|
483
|
-
`LocationCountry.tsx`/`LocationCity.tsx` prefer these over their i18n
|
|
484
|
-
translation-template + live-stats content when set.
|
|
485
|
-
|
|
486
|
-
## [1.6.9] - 2026-07-31
|
|
487
|
-
|
|
488
|
-
### Added (re-added)
|
|
489
|
-
|
|
490
|
-
- **`useRecoverFromDomCorruption()`'s global `removeChild`/`insertBefore`
|
|
491
|
-
listener is back**, generalized. Removed in 1.6.8 as a backstop
|
|
492
|
-
specifically for `GoogleReCaptchaProvider`'s remount corruption, on the
|
|
493
|
-
assumption that removing that provider removed the trigger. It didn't:
|
|
494
|
-
`@autobusal/common` 1.8.3 fixed a second, independent cause
|
|
495
|
-
(`Meta.tsx`'s stale-tag cleanup racing React 19's own `<title>`/`<meta>`
|
|
496
|
-
hoisting), and the exact same uncaught error still reproduced
|
|
497
|
-
intermittently with BOTH fixes shipped - some remaining React/router
|
|
498
|
-
timing edge case neither touches. Reinstated as a permanent safety net
|
|
499
|
-
rather than a fix for one specific bug: whatever the next contributing
|
|
500
|
-
cause turns out to be, a reload of the current URL has recovered
|
|
501
|
-
cleanly in every reproduction so far.
|
|
502
|
-
|
|
503
|
-
## [1.6.8] - 2026-07-31
|
|
504
|
-
|
|
505
|
-
### Removed
|
|
506
|
-
|
|
507
|
-
- **`Recaptcha/Recaptcha.tsx` and its `<Recaptcha>` wrapper around the
|
|
508
|
-
app in `Providers.tsx`.** This mounted `GoogleReCaptchaProvider`
|
|
509
|
-
(`react-google-recaptcha-v3`) as a persistent context provider inside
|
|
510
|
-
the same Suspense tree as `Styles`/`Notifications`/`Setup`; that
|
|
511
|
-
provider's script/badge injection is async and not remount-safe, and
|
|
512
|
-
any remount of it left the DOM in a state that could throw an
|
|
513
|
-
uncaught `removeChild`/`insertBefore` error on the next reconciliation
|
|
514
|
-
anywhere near the root - the actual root cause of the frozen-
|
|
515
|
-
navigation bug 1.6.6/1.6.7 worked around. `@autobusal/hooks` 1.3.1
|
|
516
|
-
moved reCAPTCHA to the same on-demand, no-provider pattern Turnstile
|
|
517
|
-
already used (`useRecaptcha`'s external API is unchanged), so nothing
|
|
518
|
-
needs this wrapper anymore - removing it removes the remount risk at
|
|
519
|
-
the root instead of continuing to recover from it. `useGetSettings()`
|
|
520
|
-
et al are otherwise unaffected: `Styles`/`Notifications`/`Setup` still
|
|
521
|
-
share the outer boundary exactly as before, just without `Recaptcha`
|
|
522
|
-
in the tree.
|
|
523
|
-
- **`useRecoverFromDomCorruption()`'s global `removeChild`/`insertBefore`
|
|
524
|
-
listener**, added in 1.6.7 as a backstop specifically for the
|
|
525
|
-
`GoogleReCaptchaProvider` remount corruption above. With that provider
|
|
526
|
-
gone, its trigger no longer exists.
|
|
527
|
-
|
|
528
|
-
## [1.6.7] - 2026-07-31
|
|
529
|
-
|
|
530
|
-
### Added
|
|
531
|
-
|
|
532
|
-
- **`Providers.tsx`: self-healing reload if the DOM ever corrupts near the
|
|
533
|
-
root.** 1.6.6 closed the deterministic trigger for the
|
|
534
|
-
`GoogleReCaptchaProvider` remount corruption (see 1.6.6 below), but its
|
|
535
|
-
script/badge injection is still fundamentally async - a script tag's
|
|
536
|
-
`onload` racing React's synchronous mount/cleanup - so a rare remount
|
|
537
|
-
can still leave the DOM in a state that throws an uncaught
|
|
538
|
-
`removeChild`/`insertBefore` error on the next reconciliation anywhere
|
|
539
|
-
near the root. That class of error isn't render-phase, so no
|
|
540
|
-
`ErrorBoundary` catches it, and it silently aborts whatever navigation
|
|
541
|
-
was in flight: the URL updates but the page never re-renders, with no
|
|
542
|
-
visible error and no way to navigate out. A reload of the current URL
|
|
543
|
-
always recovers cleanly (the router's `history.pushState` already
|
|
544
|
-
applied), so `Providers` now listens for exactly this error signature
|
|
545
|
-
and self-heals with one reload, guarded by a 10s `sessionStorage`
|
|
546
|
-
cooldown so a genuinely persistent error can't loop.
|
|
547
|
-
|
|
548
|
-
## [1.6.6] - 2026-07-31
|
|
549
|
-
|
|
550
|
-
### Fixed
|
|
551
|
-
|
|
552
|
-
- **`Providers.tsx`: `Setup` (and its `useGetMenu()` call) now has its own
|
|
553
|
-
inner `<Suspense>` boundary**, instead of sharing the outer one with
|
|
554
|
-
`Recaptcha`/`Styles`/`Notifications`. `Setup` calls a second,
|
|
555
|
-
independently-resolving `useSuspenseQuery` (`get-menu`) after
|
|
556
|
-
`useGetSettings()`; on a cold menu cache this suspended AFTER
|
|
557
|
-
Recaptcha's `GoogleReCaptchaProvider` had already committed and
|
|
558
|
-
injected its script/badge - and a Suspense boundary that re-suspends
|
|
559
|
-
discards its already-committed content and remounts it once ready.
|
|
560
|
-
`GoogleReCaptchaProvider` isn't remount-safe (its script/badge
|
|
561
|
-
injection is async, outside React's own DOM bookkeeping), so the
|
|
562
|
-
remount left a duplicate, half-torn-down `.grecaptcha-badge` behind.
|
|
563
|
-
That corrupted DOM state then threw an uncaught `TypeError: Cannot
|
|
564
|
-
read properties of null (reading 'removeChild')` the next time React
|
|
565
|
-
reconciled anywhere near the root - i.e. on every subsequent route
|
|
566
|
-
change, since Recaptcha wraps the whole router. The exception isn't
|
|
567
|
-
caught by the app's `ErrorBoundary` (render-phase only), so it
|
|
568
|
-
silently aborted the commit: the URL would update (the router's
|
|
569
|
-
`history.pushState` already ran) but the page itself never
|
|
570
|
-
re-rendered - freezing client-side navigation app-wide, most visibly
|
|
571
|
-
reproduced via the account dropdown menu (Password, Telegram, and the
|
|
572
|
-
impersonation-aware Logout/"Return to my account" link, which is what
|
|
573
|
-
surfaced this).
|
|
574
|
-
|
|
575
|
-
## [1.6.5] - 2026-07-31
|
|
576
|
-
|
|
577
|
-
### Added
|
|
578
|
-
|
|
579
|
-
- **`Queries/apiClient.ts` sends `Content-Language`** on every request,
|
|
580
|
-
reflecting the live active i18next language. The API's
|
|
581
|
-
ChangeApiLanguage middleware/Languaged trait expect this to localize
|
|
582
|
-
city/country/article/... names, but the frontend never sent it -
|
|
583
|
-
those names always rendered in the API's own default locale
|
|
584
|
-
regardless of the UI's actual language. Requires obtapi's
|
|
585
|
-
Traits\Languaged fix (falls back safely for the 13 UI languages the
|
|
586
|
-
database has no columns for) and @autobusal/bff 1.2.0 (forwards the
|
|
587
|
-
header upstream in bff mode) to actually take effect end-to-end.
|
|
588
|
-
|
|
589
|
-
## [1.6.4] - 2026-07-30
|
|
590
|
-
|
|
591
|
-
### Added
|
|
592
|
-
|
|
593
|
-
- **`types/locations.ts` `CityStats`/`CityDestination` types + `CityData.stats`/`.destinations`** - real route/operator/destination-count/price stats and reachable-destinations list from obtapi's new `cities/view` endpoint, for city hub landing pages.
|
|
594
|
-
|
|
595
|
-
## [1.6.3] - 2026-07-30
|
|
596
|
-
|
|
597
|
-
### Added
|
|
598
|
-
|
|
599
|
-
- **`types/locations.ts` `CountryStats` type + `CountryData.stats`** - real
|
|
600
|
-
route/operator/city/price-from counts from obtapi's enriched
|
|
601
|
-
`countries/view` endpoint, for country landing pages to render actual
|
|
602
|
-
descriptive content instead of a bare city list.
|
|
603
|
-
|
|
604
|
-
## [1.6.2] - 2026-07-30
|
|
605
|
-
|
|
606
|
-
### Fixed
|
|
607
|
-
|
|
608
|
-
- **`Setup/analytics.ts` now initializes GA4 with `send_page_view: false`.**
|
|
609
|
-
Pairs with `@autobusal/common` 1.8.0, which sends every pageview
|
|
610
|
-
explicitly (including the first) from `Meta` instead - previously the
|
|
611
|
-
automatic initial page_view and any future manual tracking would have
|
|
612
|
-
double-counted the first page of every session.
|
|
613
|
-
|
|
614
|
-
## [1.6.1] - 2026-07-30
|
|
615
|
-
|
|
616
|
-
### Fixed
|
|
617
|
-
|
|
618
|
-
- **`Organization`/`WebSite` JSON-LD `url` was wrong** (introduced in 1.6.0,
|
|
619
|
-
same day) - used `data.url` (`SettingsData.url`, from `Label::url()`),
|
|
620
|
-
which is deliberately the obtapi asset-storage path for this label's
|
|
621
|
-
uploaded files (`https://obtapi.../storage/labels/{label}`), not the
|
|
622
|
-
site's own public domain. Now uses `window.location.origin`.
|
|
623
|
-
|
|
624
|
-
## [1.6.0] - 2026-07-30
|
|
625
|
-
|
|
626
|
-
### Added
|
|
627
|
-
|
|
628
|
-
- **Sitewide `Organization` + `WebSite` JSON-LD** (`Setup/Preload.tsx`), via
|
|
629
|
-
the new `@autobusal/common` `JsonLd` component - the entity-grounding
|
|
630
|
-
signal AI assistants and Google's Knowledge Graph use to resolve "who is
|
|
631
|
-
this brand". `Organization.sameAs` is populated from
|
|
632
|
-
`preferences.social.*`. No `WebSite.potentialAction`/SearchAction -
|
|
633
|
-
that schema is for a single free-text query field (Google's sitelinks
|
|
634
|
-
searchbox); this app's search is two-field (from/to), so a SearchAction
|
|
635
|
-
here would be invalid structured data, not a working sitelinks box.
|
|
636
|
-
|
|
637
|
-
## [1.5.1] - 2026-07-30
|
|
638
|
-
|
|
639
|
-
### Fixed
|
|
640
|
-
|
|
641
|
-
- **`Errors/Message.tsx` (404 / system error page) never rendered a
|
|
642
|
-
`<title>` of its own** - relied entirely on `Preload`'s app-shell fallback
|
|
643
|
-
(the company name), and shipped `noIndex` neither. Now renders its own
|
|
644
|
-
`<Meta>` with the localized not-found/system-error message as the title
|
|
645
|
-
and `noIndex={true}` - error pages have no unique content worth indexing.
|
|
646
|
-
Pairs with `@autobusal/common` 1.5.1's title-ownership fix in the same
|
|
647
|
-
release, which also cleans up the resulting `<title>` duplication with
|
|
648
|
-
`Preload`'s fallback.
|
|
649
|
-
|
|
650
|
-
## [1.5.0] - 2026-07-30
|
|
651
|
-
|
|
652
|
-
### Fixed
|
|
653
|
-
|
|
654
|
-
- **`Setup/Preload.tsx` migrated off `react-helmet`** to React 19's native
|
|
655
|
-
`<title>`/`<meta>`/`<link>` hoisting, same root cause and fix as
|
|
656
|
-
`@autobusal/common`'s `Meta.tsx` in this same release - see that
|
|
657
|
-
package's changelog for the full diagnosis. No behavior change intended
|
|
658
|
-
beyond making the title/favicon/theme-color tags this component sets
|
|
659
|
-
actually reach the DOM, which they silently weren't.
|
|
660
|
-
|
|
661
|
-
## [1.4.1] - 2026-07-30
|
|
662
|
-
|
|
663
|
-
### Fixed
|
|
664
|
-
|
|
665
|
-
- **`<html lang>` never reflected the active language (`Setup/languages.ts`).**
|
|
666
|
-
`index.html` hardcodes `lang="en"` at build time and nothing updated it
|
|
667
|
-
afterward - every one of the 15 supported languages rendered under the same
|
|
668
|
-
`lang` attribute, a signal both search engines and screen readers rely on.
|
|
669
|
-
`languages()` now sets `document.documentElement.lang` immediately on init
|
|
670
|
-
(using the same `localStorage`-or-default resolution as the i18next `lng`
|
|
671
|
-
option) and keeps it in sync via `i18next.on('languageChanged', ...)`, so it
|
|
672
|
-
tracks every future change regardless of what triggers it.
|
|
673
|
-
|
|
674
|
-
## [1.4.0] - 2026-07-30
|
|
675
|
-
|
|
676
|
-
### Added
|
|
677
|
-
|
|
678
|
-
- **Cache-busting on language file requests (`Setup/languages.ts`).** A
|
|
679
|
-
production report showed the admin panel still displaying raw i18n keys
|
|
680
|
-
after obtapi's translation JSON was fixed and confirmed correct
|
|
681
|
-
server-side - even after multiple manual hard-reloads. `Cache-Control:
|
|
682
|
-
no-cache` on obtapi's side only asks caches to revalidate before reuse,
|
|
683
|
-
which was not reliable enough against whatever was actually caching
|
|
684
|
-
(browser and/or an intermediate proxy) in the real deployment. The
|
|
685
|
-
`loadPath` now appends `?v={{VITE_BUILD_ID}}`, a fresh value baked in at
|
|
686
|
-
build time by the consuming app's `vite.config.ts` `define` - every
|
|
687
|
-
rebuild produces a URL nothing in the chain has ever cached, independent
|
|
688
|
-
of whether that cache actually honors Cache-Control. Falls back to a
|
|
689
|
-
fixed `'dev'` string if the consumer hasn't defined `VITE_BUILD_ID` yet
|
|
690
|
-
(no worse than before). **Requires consuming apps to add a
|
|
691
|
-
`VITE_BUILD_ID` define to `vite.config.ts`** (done for magus and
|
|
692
|
-
alvavel alongside this release) for the cache-busting to take effect -
|
|
693
|
-
without it, this version behaves identically to 1.3.9.
|
|
694
|
-
- Companion fix in obtapi: `public/.htaccess` strengthened from
|
|
695
|
-
`Cache-Control: no-cache` to `no-store, no-cache, must-revalidate,
|
|
696
|
-
max-age=0` plus `Pragma`/`Expires`, so language JSON is never stored by
|
|
697
|
-
a compliant cache at all, not just revalidated.
|
|
698
|
-
|
|
699
|
-
## [1.3.9] - 2026-07-30
|
|
700
|
-
|
|
701
|
-
### Fixed
|
|
702
|
-
|
|
703
|
-
- `Errors/Message.tsx`: the crash/404 page's logo `alt` text was hardcoded to
|
|
704
|
-
`"Home"` in every locale, bypassing the `@lang/errors` translation
|
|
705
|
-
dictionary used for the rest of the page. Now reads `translations.home`.
|
|
706
|
-
Requires consuming apps' `src/lang/errors.ts` to define a `home` key per
|
|
707
|
-
locale (added to magus and alvavel alongside this release).
|
|
708
|
-
|
|
709
|
-
## [1.3.8] - 2026-07-29
|
|
710
|
-
|
|
711
|
-
### Added
|
|
712
|
-
|
|
713
|
-
- `LabelSettings.share`: admin-editable footer social-share links
|
|
714
|
-
(Facebook/X/Instagram/TikTok/LinkedIn/Trustpilot URLs), distinct from
|
|
715
|
-
the existing `LabelSettings.social` (OAuth login credentials). Feeds
|
|
716
|
-
the public `SettingsData.preferences.social` shape, unchanged.
|
|
717
|
-
|
|
718
|
-
## [1.3.7] - 2026-07-27
|
|
719
|
-
|
|
720
|
-
### Added
|
|
721
|
-
|
|
722
|
-
- `FoundData.external`/`FoundData.price.id` widened to admit external
|
|
723
|
-
bus-route provider offers (Mode A: search + redirect) - `id`/`price.id`
|
|
724
|
-
become the synthetic `ext:{provider}:{offer_id}` string, and an optional
|
|
725
|
-
`external: {provider, mode, offer_id}` discriminator marks the row so the
|
|
726
|
-
frontend never sends it to local-only endpoints (seat maps, coupons,
|
|
727
|
-
checkout). Scoped to `FoundData` only - the shared `PriceData`/`RouteData`
|
|
728
|
-
interfaces (used by operator-side price editing) are untouched.
|
|
729
|
-
|
|
730
|
-
## [1.3.6] - 2026-07-25
|
|
731
|
-
|
|
732
|
-
### Added
|
|
733
|
-
|
|
734
|
-
- `SettingsData.addons` - whether the WhatsApp/Telegram checkout addon is
|
|
735
|
-
available for this label and what it costs (never credentials), exposed
|
|
736
|
-
by obtapi's `Settings\ViewController::get()` for the checkout SPA to
|
|
737
|
-
render the addon selection at Step5.
|
|
738
|
-
|
|
739
|
-
## [1.3.5] - 2026-07-25
|
|
740
|
-
|
|
741
|
-
### Changed
|
|
742
|
-
|
|
743
|
-
- `LabelSettings.telegram` upgraded from `{enabled: boolean}` to the same
|
|
744
|
-
`{status: 'disabled'|'dedicated'|'main', price, ...}` shape as the new
|
|
745
|
-
`LabelSettings.whatsapp` - both are now paid checkout addons that can
|
|
746
|
-
route through the main label's connection instead of requiring their
|
|
747
|
-
own.
|
|
748
|
-
|
|
749
|
-
## [1.3.4] - 2026-07-25
|
|
750
|
-
|
|
751
|
-
### Added
|
|
752
|
-
|
|
753
|
-
- `LabelSettings.telegram`: admin-managed bot config (enabled/bot_token/
|
|
754
|
-
bot_username) for the Telegram route-notifications feature.
|
|
755
|
-
|
|
756
|
-
## [1.3.3] - 2026-07-25
|
|
757
|
-
|
|
758
|
-
### Added
|
|
759
|
-
|
|
760
|
-
- `LabelPayments.raiffeisen.source`: 'dedicated' | 'main' - whether this
|
|
761
|
-
label uses its own RaiAccept account or routes through the main label's
|
|
762
|
-
account/domain (RaiAccept only accepts one registered domain per
|
|
763
|
-
merchant account). See obtapi RaiffeisenController::relay().
|
|
764
|
-
|
|
765
|
-
## [1.3.2] - 2026-07-25
|
|
766
|
-
|
|
767
|
-
### Fixed
|
|
768
|
-
|
|
769
|
-
- `RouteData.passenger_fields` (route form submit shape) missing from 1.3.1.
|
|
770
|
-
|
|
771
|
-
## [1.3.1] - 2026-07-25
|
|
772
|
-
|
|
773
|
-
### Added
|
|
774
|
-
|
|
775
|
-
- `TripData.passenger_fields` (which passenger details a route requires at
|
|
776
|
-
checkout) and `TicketData.whatsapp`/`telegram` messaging contacts.
|
|
777
|
-
|
|
778
|
-
## [1.3.0] - 2026-07-25
|
|
779
|
-
|
|
780
|
-
### Changed
|
|
781
|
-
|
|
782
|
-
- **Captcha is now a runtime, per-label setting.** `Recaptcha.tsx` reads the
|
|
783
|
-
label's captcha config (provider + public site key) from `/api/settings/get`
|
|
784
|
-
via `useGetSettings()` and mounts `GoogleReCaptchaProvider` ONLY when the
|
|
785
|
-
provider is `recaptcha` (build-time `VITE_RECAPTCHA_KEY` remains as a
|
|
786
|
-
fallback). Cloudflare Turnstile needs no app-level provider. Because the
|
|
787
|
-
component now needs the query client, it moved INSIDE `<Queries>` in
|
|
788
|
-
`Providers.tsx`. `SettingsData`/`LabelSettings` types gained `captcha`.
|
|
789
|
-
|
|
790
|
-
## [1.2.12] - 2026-07-24
|
|
791
|
-
|
|
792
|
-
### Fixed
|
|
793
|
-
|
|
794
|
-
- The full user profile - name, email, and via the nested admin/operator/agent/subagent/employee/driver/visitor sub-object: phone, address, passport, date of birth, company/NIPT - was persisted verbatim to localStorage, readable at rest by any script on the origin. Only a display-only subset (id, name, name_display, type, status) is now persisted; the full object still lives in-memory during an active session exactly as before. A new bootstrap fetch (wired into Setup) re-hydrates the full profile once per app load using the same reduced signal, guarded so it never fires for a genuine guest.
|
|
795
|
-
|
|
796
|
-
## [1.2.11] - 2026-07-19
|
|
797
|
-
|
|
798
|
-
### Fixed
|
|
799
|
-
|
|
800
|
-
- bff-mode 401 no longer logs the user out when the BFF confirms the session is still valid: the apiClient now rejects the failed request (letting react-query retry) instead of tearing down auth. A single account query 401ing (transient race or a flaky upstream call) previously bounced the user to /login even though they were logged in. Bearer mode unchanged.
|
|
801
|
-
## [1.2.10] - 2026-07-19
|
|
802
|
-
|
|
803
|
-
### Fixed
|
|
804
|
-
|
|
805
|
-
- Resilient bff-mode auth: on a 401 the apiClient now confirms the session with the BFF (GET /bff/session) before logging out, and transparently retries the request once if still authenticated. Prevents a transient post-login 401 (an account query racing the login cookie hand-off) from bouncing the user straight back to the login page. Bearer mode is unchanged.
|
|
806
|
-
## [1.2.9] - 2026-07-19
|
|
807
|
-
|
|
808
|
-
### Added
|
|
809
|
-
|
|
810
|
-
- **Selectable auth transport in `Queries/apiClient.ts`.** A new
|
|
811
|
-
`VITE_AUTH_MODE` env flag (`bff` | `bearer`, defaulting to `bff`) picks how
|
|
812
|
-
the Axios client talks to the API. In `bff` mode the client targets the
|
|
813
|
-
same-origin `/bff` with `withCredentials`, relying on the httpOnly
|
|
814
|
-
`bff_session` cookie so no Bearer token or CSRF token ever lives in
|
|
815
|
-
JavaScript. `bearer` mode preserves the legacy transport (`VITE_API_OBT`
|
|
816
|
-
base URL, `withXSRFToken`, localStorage token) for brands that cannot run a
|
|
817
|
-
server-side component at their origin.
|
|
818
|
-
- **`isBffMode` export** derived from `VITE_AUTH_MODE`, re-exported from the
|
|
819
|
-
package root (`index.ts`) so consumers can branch on the active transport.
|
|
820
|
-
- **`GatewayCredentials<T>` interface in `types/settings.ts`**, modelling the
|
|
821
|
-
new per-gateway shape of `{ mode, sandbox, production }` so every payment
|
|
822
|
-
gateway can store a sandbox and a production credential set plus which one
|
|
823
|
-
is active.
|
|
824
|
-
|
|
825
|
-
### Changed
|
|
826
|
-
|
|
827
|
-
- **`Queries/apiClient.ts` request interceptor is now bearer-only.** The
|
|
828
|
-
interceptor that reads `token` from localStorage and sets the
|
|
829
|
-
`Authorization` header is only registered when not in BFF mode; in BFF mode
|
|
830
|
-
there is no token in JavaScript to attach.
|
|
831
|
-
- **`types/settings.ts` `LabelPayments` gateways reshaped.** `bkt`, `stripe`,
|
|
832
|
-
`mollie`, `raiffeisen`, and `nexi` are now wrapped in
|
|
833
|
-
`GatewayCredentials<...>` (sandbox/production/mode). The BKT credential key
|
|
834
|
-
was corrected from `authid` to `authId`, Stripe gained a `webhook_secret`
|
|
835
|
-
field, and `devpos` gained `mode`, `sandbox_url`, and `production_url` so
|
|
836
|
-
its API host is configurable per-label instead of hardcoded to the demo URL.
|
|
837
|
-
- **`Providers.tsx` wraps the tree in an outer `<Suspense>` boundary.**
|
|
838
|
-
`Styles` and `Setup` both call `useGetSettings()` (a suspense query) above
|
|
839
|
-
the existing inner boundary; on a cold settings cache their suspension had
|
|
840
|
-
no boundary to catch it. The outer boundary shows the `loading` fallback
|
|
841
|
-
during the initial settings fetch; the module-scope QueryClient means the
|
|
842
|
-
suspended remount reuses the same cache with no refetch loop.
|
|
843
|
-
- **`Notifications/Notifications.tsx` error toast duration raised to 6000ms**
|
|
844
|
-
(from the 4s default) so users are less likely to miss error notifications.
|
|
845
|
-
- **`Setup/languages.ts` i18next `debug` is now `import.meta.env.DEV`** instead
|
|
846
|
-
of hardcoded `true`, silencing i18next debug logging in production builds.
|
|
847
|
-
- **`Errors/Message.tsx` logo is now a keyboard-accessible control** (`role`,
|
|
848
|
-
`tabIndex`, `onKeyDown` for Enter/Space, `alt` text) with a matching
|
|
849
|
-
`:focus-visible` outline and `display: inline-block` in `Errors/styles.ts`.
|
|
850
|
-
|
|
851
|
-
### Fixed
|
|
852
|
-
|
|
853
|
-
- **Error page crash on non-English locales (`Errors/Message.tsx`).** The
|
|
854
|
-
translations lookup now falls back to `errors.en` when the selected locale
|
|
855
|
-
is absent from `errors.ts`; previously a non-English selection (e.g.
|
|
856
|
-
`localStorage` language `sq`) made the lookup `undefined` and crashed the
|
|
857
|
-
error page.
|
|
858
|
-
- **Crash inside the 500 response interceptor (`Queries/apiClient.ts`).** The
|
|
859
|
-
server-error message lookup now falls back to `languages.en` for the same
|
|
860
|
-
reason, so a missing locale can no longer throw from within the error
|
|
861
|
-
interceptor itself.
|
|
862
|
-
|
|
863
|
-
Authored by Ferjolt Ozuni. Consolidated from the magus and alvavel whitelabel
|
|
864
|
-
patch sets into canonical @autobusal source (eliminates per-repo patch-package
|
|
865
|
-
divergence).
|
|
866
|
-
|
|
867
|
-
## 1.8.0
|
|
868
|
-
|
|
869
|
-
`TicketData.status` ('active' | 'cancelled'). A passenger can now be
|
|
870
|
-
cancelled without the rest of the booking going with them, so consumers need
|
|
871
|
-
a way to tell. Optional - a missing value means 'active', which keeps older
|
|
872
|
-
payloads and any not-yet-rebuilt consumer type-checking.
|