@bygghammar/cms-platform 0.96.0 → 0.97.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/CHANGELOG.md CHANGED
@@ -8,6 +8,55 @@ matches `package.json` exactly. See
8
8
  Versions 0.1.0 through 0.78.0 predate this repository and were reconstructed
9
9
  from the published npm tarballs.
10
10
 
11
+ ## 0.97.0
12
+
13
+ Booking-off search landing pages. No migration: the settings live in the
14
+ existing `site_settings` table (the migration manifest is unchanged at 60). Two
15
+ exports were added — `./cms/booking-landing` and `./site/BookingOffNotice` —
16
+ and none removed. With nothing stored, every site behaves exactly as on 0.96.0.
17
+
18
+ - **Pages hidden from the website while booking is hidden** (Site settings →
19
+ Booking, after the pages that keep their booking links). While booking is
20
+ hidden, a page on this list is left out of every structured link the website
21
+ offers, and returns when booking is shown again. It stays live (200),
22
+ self-canonical, indexable and in the sitemap, so a visitor arriving from a
23
+ search result still gets the page: `pageIsIndexable` and `unlistedPages` are
24
+ unchanged, link-only stays `noindex`, offline stays a 404. Being indexable is
25
+ not a ranking promise, and the screen says so.
26
+ - **One rule for structured links** (`useDiscoverableLink`,
27
+ `isHiddenFromDiscovery`, `hiddenPaths` reason `booking-off`): header menu,
28
+ mobile menu and footer; link tiles (dropped); hero, banner, media-split,
29
+ price-table, countdown and trip buttons (dropped); media-split slide links,
30
+ icon-grid card links and Instagram links (link removed, picture or card
31
+ kept). Applied in render from the link policy the server rendered with, so
32
+ the server's HTML and later client navigations agree. Links inside rich text
33
+ are the one documented exception. `restrictToLinks` keeps `booking-off`
34
+ entries, which name public pages.
35
+ - **Popups** that lead to a hidden page are retired in the runtime the way
36
+ booking popups are; `withoutPopupsLeadingTo` lets a host withhold them on the
37
+ server.
38
+ - **The booking-off notice** (`BookingOffNotice`, placed by the page renderer's
39
+ new `notice` prop after a leading hero): a plain-text heading, message and an
40
+ optional link, from a site-wide default with optional per-page replacements,
41
+ field by field, Swedish and English. The link must be an internal path or an
42
+ `https://` address and never leads to booking or to a hidden page: hosts name
43
+ their booking page (`LinkVisibilityPolicy.bookingPagePaths`, checked when the
44
+ notice renders) and check links on save (`VisibilityDeps.noticeTargetProblem`,
45
+ with `noticeTargetProblem(href, routes)` doing the work from the host's
46
+ routes). The platform names no route.
47
+ - **Storage and staff writes**: rows `booking.landing.<page uuid>` and
48
+ `booking.landing.default`, written by `setBookingLandingCore` and
49
+ `setBookingLandingDefaultCore` — staff only, validated, compare-and-swap on
50
+ `updated_at`, audited, cache-invalidating. Protected pages and booking
51
+ exceptions are refused, and adding an exception for a landing page is refused
52
+ too (`BookingConflictError`); rows that say both are treated as an exception
53
+ only, and the Booking card warns. After a refused save the page-message editor
54
+ shows the stored version and says why, so a stale draft is never resubmitted
55
+ unseen.
56
+ - **Host wiring (optional)**: `booking.fns.setBookingLanding` and
57
+ `setBookingLandingDefault` show the new subsection; without them the Booking
58
+ card is unchanged.
59
+
11
60
  ## 0.96.0
12
61
 
13
62
  Security release. No migration, and no export was added or removed.
package/docs/PAGES.md CHANGED
@@ -541,11 +541,50 @@ Listing is a second property beside publication, and the two never touch:
541
541
  booking links in their own content. Removal (`stripBookingDeep`,
542
542
  `withoutBookingPopups`) is for the host to apply to data before it leaves the
543
543
  server; stored content is never changed.
544
+ - **Booking-off landing pages** (`cms/booking-landing`). While booking is off, a
545
+ page in this list leaves every structured link the website offers (see the
546
+ inventory below; `hiddenPaths` reason `booking-off`, `isHiddenFromDiscovery`,
547
+ `useDiscoverableLink`), but stays live, self-canonical, indexable and in the
548
+ sitemap: `pageIsIndexable` and `unlistedPages` do not change. Indexable is
549
+ not a ranking promise: removing a page's internal links can change how search
550
+ engines value it. Its booking links go as on any page, and it shows a
551
+ plain-text notice (`BookingOffNotice`, placed by the page renderer's `notice`
552
+ prop after a leading hero) with an optional link that never leads to booking
553
+ or to another hidden page. Staff write a site-wide default and optional
554
+ per-page replacements, field by field (`resolveNotice`). A page cannot be both
555
+ a booking exception and a landing page (`BookingConflictError`); stored rows
556
+ that say both are treated as an exception only, and the Booking card warns.
557
+ Offline still wins (404); link-only stays `noindex`. Protected pages are
558
+ refused. With booking on, nothing changes.
559
+
560
+ ### Booking-off landing pages: structured links (reviewed 2026-10-17)
561
+
562
+ Enforced in render from the link policy the server rendered with (and every
563
+ client navigation refreshes), so server HTML and the browser agree. Stored
564
+ content is never changed. `tests/booking-landing-discovery-inventory.test.tsx`
565
+ fails when a block type is added without being classified here.
566
+
567
+ | Surface | While booking is hidden, a link to a landing page… |
568
+ | --- | --- |
569
+ | Header menu, mobile menu, footer, header button | not rendered (`hiddenPaths`); an emptied footer column goes |
570
+ | `linkTiles` | tile dropped |
571
+ | `heroFull`, `ctaBanner`, `mediaSplit` (both buttons), `priceTable`, `countdown` (expired button), `tripList`/`courseList` button | button dropped |
572
+ | `mediaSplit` slide link, `iconGrid` card link, `instagram` profile and tiles | link removed, picture or card kept |
573
+ | Popups (link block, button or answer `openUrl`, form success `openUrl`) | popup withheld on the server (`withoutPopupsLeadingTo`) and retired in the runtime, like a booking popup |
574
+ | Booking-off notice link | dropped when it leads to booking (a booking site or the host's `bookingPagePaths`) or to a hidden page; refused at save by the host's `noticeTargetProblem` |
575
+ | `richText` | **exception**: links in editor-written text stay as written |
576
+ | `form` thank-you redirect | exempt: the result of submitting, not a link offered |
577
+ | `layout`, `faq`, `prose`, `checklist`, `imageMarquee`, `cabinTabs` | no links of their own |
578
+
579
+ Hosts add their own sections to the same rule with `useDiscoverableLink()`
580
+ (Voyara: its legacy hero buttons and destination cards).
544
581
 
545
582
  ### Storage
546
583
 
547
584
  No migration: one `site_settings` row per decision, keyed by page id —
548
- `page.listing.<uuid>`, `booking`, `booking.exception.<uuid>`. Every write is a
585
+ `page.listing.<uuid>`, `booking`, `booking.exception.<uuid>`,
586
+ `booking.landing.<uuid>` (`{ landing, notice? }`) and `booking.landing.default`
587
+ (`{ notice }`). Every write is a
549
588
  compare-and-swap on `updated_at` (`VisibilityConflictError` when someone else
550
589
  changed the row first). None is an `OVERRIDABLE_SETTING_KEYS` key, so the generic
551
590
  settings save and reset cannot reach them.
@@ -570,7 +609,11 @@ shown. The public page route takes a per-page `pagePolicy` query and adds
570
609
 
571
610
  ```ts
572
611
  pages: { fns: { setPageStatus?, setPageListing?, ... } }
573
- booking?: { fns: { getBookingSettings, setBookingEnabled, setBookingException } }
612
+ booking?: { fns: { getBookingSettings, setBookingEnabled, setBookingException,
613
+ setBookingLanding?, setBookingLandingDefault? } }
614
+ // server: VisibilityDeps.noticeTargetProblem?(href) — the host's route check
615
+ // (noticeTargetProblem(href, routes) does the work from the host's routes)
616
+ // browser policy: LinkVisibilityPolicy.bookingPagePaths — the host's booking page, every alias
574
617
  useLinkVisibility?: () => LinkVisibilityPolicy | undefined
575
618
  ```
576
619
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bygghammar/cms-platform",
3
- "version": "0.96.0",
3
+ "version": "0.97.0",
4
4
  "description": "Shared CMS platform: blocks, block types, i18n core, UI primitives and design-token contract used by every Bygghammar/Voyara site.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -210,6 +210,7 @@
210
210
  "./cms/page-publication": "./src/cms/page-publication.ts",
211
211
  "./cms/page-visibility": "./src/cms/page-visibility.ts",
212
212
  "./cms/booking-links": "./src/cms/booking-links.ts",
213
+ "./cms/booking-landing": "./src/cms/booking-landing.ts",
213
214
  "./site/routes": "./src/site/routes.ts",
214
215
  "./site/PageRenderer": "./src/site/PageRenderer.tsx",
215
216
  "./server/env": "./src/server/env.ts",
@@ -239,6 +240,7 @@
239
240
  "./site/route-index": "./src/site/route-index.ts",
240
241
  "./site/route-aliases": "./src/site/route-aliases.ts",
241
242
  "./site/link-visibility": "./src/site/link-visibility.tsx",
243
+ "./site/BookingOffNotice": "./src/site/BookingOffNotice.tsx",
242
244
  "./site/SiteHeader": "./src/site/SiteHeader.tsx",
243
245
  "./site/SiteFooter": "./src/site/SiteFooter.tsx",
244
246
  "./server/schemas": "./src/server/schemas.ts",
@@ -37,6 +37,8 @@ export const ACTION_LABELS: Record<string, string> = {
37
37
  "page.listing": "Changed whether a page is listed",
38
38
  "booking.settings": "Switched booking on or off",
39
39
  "booking.exception": "Changed which pages keep booking links",
40
+ "booking.landing": "Changed which pages are hidden from the website while booking is hidden",
41
+ "booking.landing.default": "Changed the booking-off landing page message",
40
42
  "page.version.create": "Saved page version",
41
43
  "page.version.restore": "Restored page version",
42
44
  "template.create": "Created block template",