@levo-so/site-runtime 0.3.21 → 0.3.23
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 +352 -0
- package/index.d.ts +56 -1
- package/index.js +414 -367
- package/package.json +3 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# @levo-so/site-runtime
|
|
2
|
+
|
|
3
|
+
## 0.3.23
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Two records in one URL segment. `levoParams(key, pattern, [siblings])` declares how a route's single param splits into several record variables, e.g. `/health-packages/<package>-in-<city>`:
|
|
8
|
+
|
|
9
|
+
- `const split = levoParams("packages_in_cities", "{$packages}-in-{$cities}")` goes on the route as `params: split`; the page reads `packages` and `cities` from `Route.useParams()`.
|
|
10
|
+
- The segment splits on its rightmost separator. A segment that does not fit is declined, so a plain sibling page (`$packages.tsx`) keeps serving `/health-packages/<package>`.
|
|
11
|
+
- `split.link(values)` is the `params` of a `<Link>` to the page (TanStack types it from every parsed param, so the segment alone does not compile); `split.segment(values)` is the string form.
|
|
12
|
+
- A plain variable beside the split in the same route is named in the third argument: `levoParams("c_in_d", "{$c}-in-{$d}", ["a"])`.
|
|
13
|
+
- Both arguments must be string literals, or top-level consts holding them: the platform reads the declaration out of the route file to enumerate the page's sitemap URLs and pairs each variable with the record binding whose `param` names it.
|
|
14
|
+
|
|
15
|
+
Loud failures for URL and binding mismatches:
|
|
16
|
+
|
|
17
|
+
- A record binding whose `param` names no param of the route throws on staging previews with the route's params listed, and logs on production sites while the record resolves to null as before.
|
|
18
|
+
- A `levoParams` key the route does not have is declined and reported once in the server log; the type check and the platform's publish check refuse it.
|
|
19
|
+
- Pattern errors (no variable, duplicate name, two variables with nothing between them, stray braces) throw when the route module initialises.
|
|
20
|
+
|
|
21
|
+
Requires the matching platform release for sitemap enumeration and publish validation; sites must pin this version for the builder AI to use the feature.
|
|
22
|
+
|
|
23
|
+
- @levo-so/core@0.3.23
|
|
24
|
+
- @levo-so/insights@0.3.23
|
|
25
|
+
|
|
26
|
+
## 0.3.22
|
|
27
|
+
|
|
28
|
+
### Internal
|
|
29
|
+
|
|
30
|
+
- The package now ships its `CHANGELOG.md`, which Studio's runtime update hands to Levo AI.
|
|
31
|
+
- The data client drops the `richtext` field type, which nothing used. Nothing to change in site code.
|
|
32
|
+
|
|
33
|
+
## 0.3.21
|
|
34
|
+
|
|
35
|
+
### Behaviour changes
|
|
36
|
+
|
|
37
|
+
- **Collection forms take bookings on generated sites.** The booking flow — billing, booking card, payment — moved into the shared form library, so a `LevoForm` bound to a priced collection now goes on to billing and payment instead of saving the entry and stopping. It is styled with the form's own theme, so the site's brand colour and dark mode carry through into billing and the ticket.
|
|
38
|
+
- Whether a submission is a booking or an enquiry is decided before billing. One with no matching price row is saved and gets the form's thank-you screen instead of a blank billing screen. When unsure (fixed price, older settings, a failed lookup) it is treated as a booking.
|
|
39
|
+
- A failed booking keeps the visitor on billing to retry against the same entry, and the redirect after payment follows the form's success action.
|
|
40
|
+
- Requests start their 30-second timeout when the call is made, not when the page loaded, so an action taken more than 30 seconds after page load no longer aborts before it is sent.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- The Stripe payment window keeps keyboard focus inside itself; Tab no longer walks onto the page behind it.
|
|
45
|
+
|
|
46
|
+
### Internal
|
|
47
|
+
|
|
48
|
+
- Dependency security updates. The upgrade's install picks them up; nothing to change in site code.
|
|
49
|
+
|
|
50
|
+
## 0.3.20
|
|
51
|
+
|
|
52
|
+
### Behaviour changes
|
|
53
|
+
|
|
54
|
+
- **Event registration is one sheet.** `LevoEventRegister` opens a single scrolling sheet instead of a two-step modal: ticket choice with nothing preselected, a collapsible order summary, inline coupons, attendee details, payment, and an in-sheet confirmation with add-to-calendar that survives a reload. A failed payment returns to the filled-in form, and "Pay again" retries after a closed or failed payment. Signed-in visitors are prefilled. It uses the site's theme colours, so it follows the brand and dark mode.
|
|
55
|
+
- Cashfree checkout for event registration, alongside Razorpay and Stripe.
|
|
56
|
+
- The registration form's fields follow the event's `registration_fields`.
|
|
57
|
+
|
|
58
|
+
### New
|
|
59
|
+
|
|
60
|
+
- `LevoEventRegister` optional props `startsAt`, `endsAt` (the event's `starts_at` / `ends_at`, shown in the visitor's timezone and used for the calendar entry) and `eventUrl` (the joining link, attached to the calendar entry).
|
|
61
|
+
- `?register=1` on a page URL opens the registration sheet on load — for campaign links and second calls to action.
|
|
62
|
+
|
|
63
|
+
### Site changes (optional)
|
|
64
|
+
|
|
65
|
+
- Where a page renders `LevoEventRegister` with the event record at hand, pass `startsAt={event.starts_at}`, `endsAt={event.ends_at}` and `eventUrl={event.url}` so the sheet shows the time and the calendar entry has the link. Without them it works as before.
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- Restores the form fixes from 0.3.16, 0.3.17 and 0.3.18 that 0.3.19 was published without (see 0.3.19, Known issue).
|
|
70
|
+
|
|
71
|
+
## 0.3.19
|
|
72
|
+
|
|
73
|
+
### Breaking
|
|
74
|
+
|
|
75
|
+
- **The runtime no longer adds the favicon.** `levoHead` (`createLevoHead`) used to emit `<link rel="icon">` from `site.json`; it no longer does, so a site that relied on it shows the browser's fallback icon (the React logo from `public/favicon.ico`, if that file is still there). Declare exactly one icon in `src/routes/__root.tsx`, in `head().links`, replacing any existing icon link:
|
|
76
|
+
`{ rel: "icon", href: site.icon?.location ?? "https://app.levo.so/favicon.ico" }`
|
|
77
|
+
with `import { LevoSiteProvider, site } from "@/lib/levo/runtime";`. If `src/lib/levo/runtime.tsx` has no `site` export (sites created before August), add it there: `export const site: ISiteJson = siteJson;`, importing `type ISiteJson` from `@levo-so/site-runtime`.
|
|
78
|
+
- `ISiteMeta.icon` is removed. A `levoHead`/`pageHead` call that passes `icon` fails typecheck — delete the field.
|
|
79
|
+
|
|
80
|
+
### Site changes (optional)
|
|
81
|
+
|
|
82
|
+
- Add `{ title: site.name }` to the root route's `head().meta`, the fallback title for 404s.
|
|
83
|
+
- Delete `public/favicon.ico`, `public/logo192.png`, `public/logo512.png` and `public/manifest.json` if nothing references them — leftovers from the React template.
|
|
84
|
+
|
|
85
|
+
### Known issue
|
|
86
|
+
|
|
87
|
+
- This build was published without the form fixes from 0.3.16, 0.3.17 and 0.3.18: forms behave as they did in 0.3.15. 0.3.20 restores them. Nothing to change in site code.
|
|
88
|
+
|
|
89
|
+
## 0.3.18
|
|
90
|
+
|
|
91
|
+
### Fixed
|
|
92
|
+
|
|
93
|
+
- Forms (`LevoForm`, `LevoEntryForm`, `LevoProfileForm`, popup forms) hide fields the collection marks as not publicly viewable or submittable (`allow_public_view: false`, `allow_public_submit: false`), including sub-fields. A `hiddenFields` list that arrives after the first render is respected.
|
|
94
|
+
|
|
95
|
+
## 0.3.17
|
|
96
|
+
|
|
97
|
+
### Fixed
|
|
98
|
+
|
|
99
|
+
- A phone, date-time or rich-text field revealed by form logic no longer blanks and resets the whole form.
|
|
100
|
+
|
|
101
|
+
## 0.3.16
|
|
102
|
+
|
|
103
|
+
### Fixed
|
|
104
|
+
|
|
105
|
+
- Form logic: a section whose fields are all hidden no longer leaves an empty gap; step forms skip steps that would be empty; hiding is transitive (if A hides B, fields that depend on B hide too); hidden fields are cleared on submit.
|
|
106
|
+
|
|
107
|
+
## 0.3.15
|
|
108
|
+
|
|
109
|
+
### Fixed
|
|
110
|
+
|
|
111
|
+
- Multi-language sites: hovering a link to an unprefixed path (`/pricing` instead of `/en/pricing`) froze the tab. `localeBeforeLoad` now redirects without a loop and keeps the query string. Nothing to change in site code.
|
|
112
|
+
- `LevoBlogContent` renders self-hosted video blocks; YouTube and iframe embeds honour a percent width and left/center/right alignment; YouTube embeds keep 16:9 instead of a fixed 480px height; iframes are at least 100px tall.
|
|
113
|
+
|
|
114
|
+
### Site changes (optional)
|
|
115
|
+
|
|
116
|
+
- `src/components/language-switcher.tsx`, if present: put `key={locale}` before `{...linkProps}` on the `<a>`. Stops a React "unique key" warning on every page load.
|
|
117
|
+
|
|
118
|
+
### Internal
|
|
119
|
+
|
|
120
|
+
- Chat widget dependencies (`@assistant-ui/*`) bumped. The upgrade's install picks them up.
|
|
121
|
+
|
|
122
|
+
## 0.3.14
|
|
123
|
+
|
|
124
|
+
### Fixed
|
|
125
|
+
|
|
126
|
+
- Links inside `LevoBlogContent` no longer carry `rel="nofollow"` or `rel="noreferrer"`; `rel="noopener"` is added only next to `target="_blank"`. Applies to posts already published.
|
|
127
|
+
- Blockquotes inside blog content use the body text size and colour instead of a smaller muted grey — a visible change on posts with quotes.
|
|
128
|
+
|
|
129
|
+
## 0.3.13
|
|
130
|
+
|
|
131
|
+
### Breaking
|
|
132
|
+
|
|
133
|
+
- The data client's blog and event reads now require their options bag: `blog.getAllBlogs(key, query, options)`, `blog.getSingleBlog(key, slug, options)`, `event.getAllEvents(seriesKey, query, options)`, `event.getSingleEvent(slug, options)`, where `options` is `{ workspace_id: string; params?: Record<string, string> }`. Only code that calls the client from `useLevoSite()` directly is affected — `useLevoSource`, `useLevoRecord` and route `dataSources` are not. Those calls were already failing at runtime (events answered 400, blog lists came back empty).
|
|
134
|
+
|
|
135
|
+
### Site changes
|
|
136
|
+
|
|
137
|
+
- Record bindings take `param`, the route param they read, spelled exactly like the `$segment` in the route's file name: `{ mode: "record", module: "blog", identifier: "so_levo_blog", param: "slug" }`. **Required on a route with two or more `$params`** (e.g. `src/routes/stores/$state/$city.tsx`): without it the loader looks for `params.slug`, finds nothing, and the page renders with no server-side data, title or share image. A route with a single `$param` falls back to it, so `param` is optional there.
|
|
138
|
+
|
|
139
|
+
### Fixed
|
|
140
|
+
|
|
141
|
+
- Blog and event data sources send the workspace, so event pages stop answering 400 and blog lists stop coming back empty.
|
|
142
|
+
- A detail route whose param isn't `slug` (e.g. `/legal-events/$events`) now prefetches its record, so it gets its per-record title, description and share image.
|
|
143
|
+
- Visitor chat: tables in replies get a "Copy table" button.
|
|
144
|
+
|
|
145
|
+
## 0.3.12
|
|
146
|
+
|
|
147
|
+
### Breaking
|
|
148
|
+
|
|
149
|
+
- **`LevoRichText` is renamed `LevoBlogContent`**, and its subpath moves: `@levo-so/site-runtime/rich-text` → `@levo-so/site-runtime/blog-content`; `ILevoRichTextProps` → `ILevoBlogContentProps`. Same props (`content` plus div attributes). No alias: the old import fails typecheck. Fix: `Grep` for `LevoRichText` and `site-runtime/rich-text`, then rename the import and the tag. Where it wraps a plain string (a title, an excerpt, a description), remove the wrapper and render the string instead — the component is only for a stored editor document, i.e. a blog post's `content` or a rich-text collection field.
|
|
150
|
+
|
|
151
|
+
### New
|
|
152
|
+
|
|
153
|
+
- `useLevoSearch({ limit?, debounceMs?, hotkey? })` — site-wide search behaviour: `query`/`setQuery`, debounced `results`, `isLoading`, `error`, `isEmpty`, and a ⌘K open state (`isOpen`, `open`, `close`). Renders nothing; must sit under `LevoSiteProvider`. Each result is `ILevoSearchResult { content_type, content_id, title, description, url, thumbnail, score }`.
|
|
154
|
+
- `LevoForm` optional props: `defaultValues` (prefill by collection field key), `onSuccess(result)` and `onError(error)`, `showSuccessScreen={false}` (keep the form instead of the thank-you card), `disabled`. Types `ILevoFormSubmitResult`, `ILevoFormSuccessConfig`.
|
|
155
|
+
- `LevoSiteProvider` takes `analytics?: boolean` (default `true`). Sites created from this version pass `analytics={import.meta.env.PROD}` in `src/lib/levo/runtime.tsx` so previews stop counting as visits.
|
|
156
|
+
|
|
157
|
+
### Site changes (optional)
|
|
158
|
+
|
|
159
|
+
- Sites created from this version ship `src/components/search/SiteSearch.tsx`, `src/components/access/LoginModal.tsx` and `src/components/access/UserMenu.tsx`. An older site does not have them — never import one without checking it exists; build the feature from the hooks instead.
|
|
160
|
+
|
|
161
|
+
### Behaviour changes
|
|
162
|
+
|
|
163
|
+
- Inside the Studio editor preview, the chat launcher and the "Made with Levo" badge are hidden. The preview URL and the live site are unchanged.
|
|
164
|
+
- The editor can force a gated page's outcome (granted, sign-in, blocked) to design it. On the live site the real session still decides, and the data behind the gate stays protected.
|
|
165
|
+
|
|
166
|
+
## 0.3.11
|
|
167
|
+
|
|
168
|
+
### Behaviour changes
|
|
169
|
+
|
|
170
|
+
- Analytics now creates visitor ids in the browser and sets first-party cookies `id_i_cd` (device) and `id_i_cs` (session) plus localStorage `lv_insights_identity`; `lv_aud_*` and `lv_insights_sf` are no longer written. Update any cookie banner or privacy page that names cookies. No code changes.
|
|
171
|
+
|
|
172
|
+
## 0.3.10
|
|
173
|
+
|
|
174
|
+
### New
|
|
175
|
+
|
|
176
|
+
- Data sources read blog categories, tags and authors (`module: "blog_category" | "blog_tag" | "blog_author"`, `identifier` = the parent blog's key) and community forum posts (`module: "community"`, `identifier` = the forum key). Type `ILevoSourceModule`.
|
|
177
|
+
- `LevoRichText` from `@levo-so/site-runtime/rich-text` — renamed in 0.3.12; do not use this name.
|
|
178
|
+
|
|
179
|
+
### Behaviour changes
|
|
180
|
+
|
|
181
|
+
- `LevoSiteProvider` shows a small "Made with Levo" badge in the bottom-right corner when the workspace's plan requires branding (`site.json` → `feature_flag.branding`). Remove any hand-built Levo badge so it doesn't show twice.
|
|
182
|
+
- `@levo-so/site-runtime/setup.css` now includes the blog-content prose styles.
|
|
183
|
+
- Visitor chat shows when each message was sent ("9:42 AM", "Aug 19 · 9:42 AM").
|
|
184
|
+
|
|
185
|
+
### Site changes (optional)
|
|
186
|
+
|
|
187
|
+
- `__root.tsx` `head().links`: `{ rel: "preconnect", href: "https://static.levocdn.com" }` makes Levo-hosted images start loading sooner.
|
|
188
|
+
|
|
189
|
+
## 0.3.9
|
|
190
|
+
|
|
191
|
+
### New
|
|
192
|
+
|
|
193
|
+
- Multi-language sites. Inert until the workspace has two or more locales; single-language sites are unaffected.
|
|
194
|
+
- `@levo-so/site-runtime/start` (new subpath): `createLocaleMiddleware(config, routeTree)`, used in `src/start.ts`.
|
|
195
|
+
- `readLocaleConfig(site)`, `localeBeforeLoad(config, routeTree)`, `SiteLocaleProvider`, `LocaleHreflang`, `useUrlLocale(config)`, `useLocale()`, `useLanguageOptions()` (link props for a language switcher).
|
|
196
|
+
- `ISiteJson` gains `locales` and `default_locale`.
|
|
197
|
+
- Do not add this wiring during an upgrade. It is only needed once a workspace adds a second language, and switching it on moves every URL under a language prefix. When wiring it, never import `@/routeTree.gen` into `__root.tsx` — the import cycle makes every server render fail.
|
|
198
|
+
|
|
199
|
+
### Behaviour changes
|
|
200
|
+
|
|
201
|
+
- Canonical URLs drop the trailing slash (`/blog/` → `/blog`; the root keeps its `/`). Visible to search engines.
|
|
202
|
+
- New-entry forms prefill the collection's default values. Clearing a relation field now saves (it is sent as empty instead of left out).
|
|
203
|
+
|
|
204
|
+
### Internal
|
|
205
|
+
|
|
206
|
+
- Tiptap bumped to 3.30, Stripe and Razorpay pinned; new optional peer `@tanstack/react-start`, which every site already has. The upgrade's install picks them up.
|
|
207
|
+
|
|
208
|
+
## 0.3.8
|
|
209
|
+
|
|
210
|
+
### New
|
|
211
|
+
|
|
212
|
+
- Members-only pages (page gating):
|
|
213
|
+
- `useLevoAccess({ id, access })` — the page gate: `state` (`checking | granted | blocked`), `code`, `rule`, `authMethods`, `recheck`. Stays closed on any failure.
|
|
214
|
+
- `useLevoAuth()` (the visitor's session: `account`, `isLoggedIn`, `isLoaded`, `refresh`, `signOut`), `useLevoLogin()` (`sendCode`, `resendCode`, `verifyCode`, `sendMagicLink`, each returning `{ ok, data | error }`), `useLevoOAuth(...)` (Google, Microsoft, LinkedIn popup sign-in), `useLevoOtpInput(...)`.
|
|
215
|
+
- `LevoEntryForm` (request access to a collection), `LevoProfileForm` (complete the member profile).
|
|
216
|
+
- Validators `isValidEmail`, `isValidOtp`, `isValidPhone`, `normalizePhone`; `LEVO_OTP_LENGTH`.
|
|
217
|
+
- The loader from `createLevoLoader(site)` takes the route's meta as an optional second argument: `loader: levoLoader(dataSources, meta)`. A gated page then skips its server-side prefetch, so its data never reaches a visitor the gate would refuse.
|
|
218
|
+
|
|
219
|
+
### Site changes (only for gated pages)
|
|
220
|
+
|
|
221
|
+
- Sites created from this version ship the gate UI in `src/components/access/` (`AccessGate.tsx`, `LoginCard.tsx` and the rest). An older site does not have that folder — never import from `@/components/access/` without checking it exists; build the gate from `useLevoAccess` and `useLevoLogin` instead.
|
|
222
|
+
|
|
223
|
+
### Fixed
|
|
224
|
+
|
|
225
|
+
- A form with a phone, date-time or rich-text field no longer blanks the page while those fields load.
|
|
226
|
+
|
|
227
|
+
## 0.3.7
|
|
228
|
+
|
|
229
|
+
### Behaviour changes
|
|
230
|
+
|
|
231
|
+
- Visitor chat: on a site with several chat agents, the agent follows the page while the conversation carries over; reloading the page starts a fresh conversation; suggested questions come from the current agent and fill the input instead of sending; while an answer streams, Stop is the only action.
|
|
232
|
+
- Event registration (`LevoEventRegister`): the tax/GST block is hidden when nothing is payable, and the dialog scrolls inside itself on short screens.
|
|
233
|
+
- Popups: a redirect popup's cover image is clickable; popup content loads when the popup opens.
|
|
234
|
+
|
|
235
|
+
### Internal
|
|
236
|
+
|
|
237
|
+
- Chat widget dependencies (`@assistant-ui/*`) move from 0.12 to 0.15. The chat panel breaks on first open if the site's lockfile keeps the old versions; the upgrade's install picks up the new ones.
|
|
238
|
+
|
|
239
|
+
## 0.3.1
|
|
240
|
+
|
|
241
|
+
### Patch Changes
|
|
242
|
+
|
|
243
|
+
- 88218da: Sites now start insights, expose the runtime handles, and carry their page id.
|
|
244
|
+
|
|
245
|
+
- `LevoSiteProvider` calls `insights.init()` after mount. Nothing on a code-routed site did — Levo-hosted pages get it from `LevoPage`'s `LevoInsightsPage` — so `$isIdentityContextReady` never flipped and every store-only consumer (popup triggers, DOM trackers) waited forever. Popups were silently dead on generated sites.
|
|
246
|
+
- `window.levo_client` / `window.levo_insights` are set on generated sites, matching what `LevoPage` exposes on Levo-hosted ones.
|
|
247
|
+
- Page identity flows route → runtime: `meta/<route>.json`'s `id` (new `IPlatformMetaFile.id`) is stamped into the head by `levoHead` and read back by the provider on mount and on every client navigation (`readPageId` / `PAGE_ID_META`, both exported). It feeds analytics `page_id` and `LevoPopups`' new `pageId` prop, which makes page-bound popup targeting work on code-routed sites. Sites whose meta files carry no id are unaffected — URL-path targeting behaves as before.
|
|
248
|
+
|
|
249
|
+
### Patch Changes
|
|
250
|
+
|
|
251
|
+
- insights and popup fixes
|
|
252
|
+
|
|
253
|
+
## 0.3.0
|
|
254
|
+
|
|
255
|
+
### Minor Changes
|
|
256
|
+
|
|
257
|
+
- 1e929c2: Inspector: selection captures for the editor's AI chat.
|
|
258
|
+
|
|
259
|
+
- `capture-selected` message — rasterizes the currently selected element to a PNG data URL (html-to-image, lazy-loaded chunk) and replies `capture-result`.
|
|
260
|
+
- Select-mode marquee drag (region select): press-drag draws a dashed rubber-band; release reports `region-selected` with the dragged rect, the topmost source-stamped elements substantially inside it (capped at 12), and a PNG of exactly that area. Sub-threshold presses remain single-element click picks; Esc mid-drag cancels the marquee only. Driven by pointer events (the swallow layer's `pointerdown` preventDefault suppresses all compatibility mouse events — mouse-based drag listeners never fire in select mode). Pointer capture at drag activation makes the gesture edge-tolerant: overshooting the iframe boundary clamps the marquee to the frame instead of cancelling, and releasing outside still finalizes.
|
|
261
|
+
- Region highlights: after a drag, every included element gets a persistent green ring (fixed pool, part of the inspector's own overlay set) — repositioned on scroll/resize, suppressed in text mode, cleared by the same paths as the single-pick ring (new pick, clear-selection, route change).
|
|
262
|
+
- Correct capture backgrounds: renders are subtree-only, so ancestor-painted backgrounds used to come out transparent ("wrong bg"). Element captures now fill with the effective ancestor background color; region captures climb to the nearest background-painting ancestor as the render root (bounded at 4 viewport-areas) so crops include real gradient/image backgrounds, with the color fill as fallback. The intermediate region raster is capped at a 4000px longest edge so a small drag inside a large painted section can't spike memory or stall the preview.
|
|
263
|
+
|
|
264
|
+
### Patch Changes
|
|
265
|
+
|
|
266
|
+
- select element ss
|
|
267
|
+
|
|
268
|
+
## 0.2.3
|
|
269
|
+
|
|
270
|
+
### Patch Changes
|
|
271
|
+
|
|
272
|
+
- dark mode, popups, site agents
|
|
273
|
+
|
|
274
|
+
## 0.2.2
|
|
275
|
+
|
|
276
|
+
### Patch Changes
|
|
277
|
+
|
|
278
|
+
- image edit and better shortcut support
|
|
279
|
+
|
|
280
|
+
## 0.2.1
|
|
281
|
+
|
|
282
|
+
### Patch Changes
|
|
283
|
+
|
|
284
|
+
- fa254dc: Studio v3 sites can now render events and take registrations.
|
|
285
|
+
|
|
286
|
+
- Data sources: `module: "event"` works with `useLevoSource` (events in a series, keyed
|
|
287
|
+
by the series id) and `useLevoRecord` (one event detail page by slug).
|
|
288
|
+
- New `LevoEventRegister` component — a self-contained ticket booking + payment widget
|
|
289
|
+
(Razorpay by default, Stripe Embedded Checkout when the booking API returns
|
|
290
|
+
`provider: "stripe"`). Generated event pages drop this in and never rebuild the flow.
|
|
291
|
+
- Adds `@stripe/stripe-js`, `@stripe/react-stripe-js`, `react-razorpay` dependencies and
|
|
292
|
+
`lucide-react` + `react-dom` peer dependencies. The registration dialogs are built on
|
|
293
|
+
Base UI (`@base-ui/react`, an optional peer) for focus trapping, scroll lock, and
|
|
294
|
+
accessible dialog semantics.
|
|
295
|
+
|
|
296
|
+
- fa254dc: Collection-backed forms. Generated sites can now collect submissions into a collection.
|
|
297
|
+
|
|
298
|
+
- New `mode: "form"` data source — the route loader prefetches the collection SCHEMA
|
|
299
|
+
(`getCollectionByIDExpanded`) SSR via `prefetchLevoSources`, and `useLevoForm(config)`
|
|
300
|
+
reads it from the warm cache.
|
|
301
|
+
- New `LevoForm` component — renders the collection's fields, validates, submits, shows a
|
|
302
|
+
success screen, and handles captcha. It reuses the shared `CollectionFormRenderer`, which
|
|
303
|
+
resolves its client from `@levo-so/react`'s `useLevo()` context. `LevoSiteProvider` now
|
|
304
|
+
mounts that context at the site root (it delegates to `@levo-so/react`'s `LevoProvider`,
|
|
305
|
+
a trivial SSR-safe context after the P5 SDK diet), so the renderer works with no per-form
|
|
306
|
+
provider bridge. `useLevoSite`/`useLevoInsights` are null-safe re-exports of
|
|
307
|
+
`@levo-so/react`'s `useLevoClient`/`useLevoInsights`.
|
|
308
|
+
- `LevoForm` auto-themes to the site: it maps the renderer's full color contract onto the
|
|
309
|
+
starter's semantic tokens (`--color-brand`, `--color-ink`, `--color-border`…) via levocn's
|
|
310
|
+
`inlineBaseTheme`, so a form matches the brand out of the box and re-skins when `theme.css`
|
|
311
|
+
changes. Its prop surface is intentionally narrow (`source`, `submitLabel`, `hiddenFields`,
|
|
312
|
+
`formVariant`, `successConfig`, `theme`, `className`) rather than exposing the renderer's
|
|
313
|
+
full context; `theme` allows per-form color/radius overrides.
|
|
314
|
+
- The form renderer (`@levo.libraries/react-collection` + `editor-core` + `levocn`) and the
|
|
315
|
+
`@levo-so/react` client context it depends on are now BUNDLED into this package's dist
|
|
316
|
+
(mirroring how `@levo-so/studio` ships). A consuming site installs only
|
|
317
|
+
`@levo-so/site-runtime` from public npm — no private registry, and `@levo-so/react` never
|
|
318
|
+
appears as a dependency. The ~40 public leaf deps (tiptap, yup, react-select, sonner…) are
|
|
319
|
+
hoisted into this package. Bundled styles ship as `@levo-so/site-runtime/setup.css`.
|
|
320
|
+
- Build change: switched from per-module output to studio-style bundled output
|
|
321
|
+
(`publishConfig.directory: "dist"`, generated `dist/package.json`, rolled-up types).
|
|
322
|
+
|
|
323
|
+
- full cleanup
|
|
324
|
+
- Updated dependencies [fa254dc]
|
|
325
|
+
- Updated dependencies
|
|
326
|
+
- @levo-so/core@0.2.1
|
|
327
|
+
- @levo-so/insights@0.2.1
|
|
328
|
+
|
|
329
|
+
## 0.2.0
|
|
330
|
+
|
|
331
|
+
### Minor Changes
|
|
332
|
+
|
|
333
|
+
- 54e5118: Package the scaffold contract so starters stay thin shims: `createLevoHead(siteJson)` —
|
|
334
|
+
each route imports its own `meta/<route>.json` (`@meta/` alias) and wires
|
|
335
|
+
`head: levoHead(meta)`; composes the platform meta keys (incl. `ld_json`, `canonical`,
|
|
336
|
+
`twitter_card`, `head_code`, `disable_indexing`) with site.json fallbacks
|
|
337
|
+
("Page name | Site name") into the document head. Plus `createLevoLoader` (the SSR
|
|
338
|
+
data-source route loader) and `siteConfigFromJson` (site.json → ILevoSiteConfig). The
|
|
339
|
+
starter's single `src/lib/levo/runtime.tsx` shim only feeds in `site.json`; every future
|
|
340
|
+
behavior change ships by bumping this package.
|
|
341
|
+
|
|
342
|
+
### Patch Changes
|
|
343
|
+
|
|
344
|
+
- updated meta folders
|
|
345
|
+
|
|
346
|
+
## 0.1.113
|
|
347
|
+
|
|
348
|
+
### Patch Changes
|
|
349
|
+
|
|
350
|
+
- Updated dependencies [d885aa0]
|
|
351
|
+
- @levo-so/core@0.1.113
|
|
352
|
+
- @levo-so/insights@0.1.113
|
package/index.d.ts
CHANGED
|
@@ -883,6 +883,51 @@ export declare interface ILevoOtpSlot {
|
|
|
883
883
|
};
|
|
884
884
|
}
|
|
885
885
|
|
|
886
|
+
/**
|
|
887
|
+
* One split segment, declared once: the route's `params` option IS this object, and every
|
|
888
|
+
* link to the route reuses the same const, so the pattern is never spelled twice.
|
|
889
|
+
* `TSiblings` names the route's other required params, `never` when the segment is the only
|
|
890
|
+
* one (see `levoParams`).
|
|
891
|
+
*/
|
|
892
|
+
export declare interface ILevoParams<TSegment extends string, TPattern extends string, TSiblings extends string = never> {
|
|
893
|
+
/**
|
|
894
|
+
* TanStack's `params.parse`: the variables split out of the segment beside the raw key and
|
|
895
|
+
* the siblings, or `false` for a segment the pattern does not fit, which declines the route
|
|
896
|
+
* so the sibling route with no pattern serves the URL. Throws when the key is not a param of
|
|
897
|
+
* the route at all (the first argument misspells the file's `$param`): see `parse` below.
|
|
898
|
+
*/
|
|
899
|
+
parse: (raw: Record<TSegment | NoInfer<TSiblings>, string>) => ILevoParsedParams<TSegment, TPattern, TSiblings> | false;
|
|
900
|
+
/**
|
|
901
|
+
* The segment the values compose (`"full-body-in-mumbai"`), for a string href: a JSON-LD
|
|
902
|
+
* url, a sitemap-style index. A missing or empty value throws: a link with a hole is a bug,
|
|
903
|
+
* not a URL.
|
|
904
|
+
*/
|
|
905
|
+
segment: (values: Record<ILevoPatternVars<TPattern>, string>) => string;
|
|
906
|
+
/**
|
|
907
|
+
* The `params` of a `<Link>` to the route: the values plus the composed segment under the
|
|
908
|
+
* raw key, with the sibling values passed through so a `<Link to="/$a/$c_in_d">` gets `a`
|
|
909
|
+
* beside the segment. Exists because TanStack types `Link params` from the route's PARSED
|
|
910
|
+
* params, so the variables are required beside the segment and the segment alone does not
|
|
911
|
+
* compile. Throws for a missing or empty value, as `segment` does.
|
|
912
|
+
*/
|
|
913
|
+
link: (values: Record<ILevoPatternVars<TPattern> | TSiblings, string>) => ILevoParsedParams<TSegment, TPattern, TSiblings>;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* What `parse` returns and `link` composes: the route's raw segment param (TanStack's
|
|
918
|
+
* parsed-params type must keep it), the route's other required params named as siblings
|
|
919
|
+
* (TanStack types the route's params from this alone, so a sibling not named here drops out
|
|
920
|
+
* of the route's types), plus one string per pattern variable.
|
|
921
|
+
*/
|
|
922
|
+
export declare type ILevoParsedParams<TSegment extends string, TPattern extends string, TSiblings extends string = never> = Record<TSegment | TSiblings, string> & Record<ILevoPatternVars<TPattern>, string>;
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* The variable names of a pattern as a union of literals:
|
|
926
|
+
* `ILevoPatternVars<"{$packages}-in-{$cities}">` is `"packages" | "cities"`.
|
|
927
|
+
* A pattern that is not a literal has no inferable names (`never`).
|
|
928
|
+
*/
|
|
929
|
+
export declare type ILevoPatternVars<TPattern extends string> = TPattern extends `${string}{$${infer TName}}${infer TRest}` ? TName | ILevoPatternVars<TRest> : never;
|
|
930
|
+
|
|
886
931
|
export declare interface ILevoPopupsProps {
|
|
887
932
|
/**
|
|
888
933
|
* The site's id (`site.json` `id`) — scopes the active-popups fetch. Absent → the
|
|
@@ -921,7 +966,9 @@ export declare interface ILevoRecordConfig {
|
|
|
921
966
|
* Route param this binding's slug comes from — `"slug"` on `/blog/$slug`, `"events"` on
|
|
922
967
|
* `/legal-events/$events`. Every generated record binding carries it: it is the mapping a
|
|
923
968
|
* multi-param route needs, since with two variable segments nothing else in the file says
|
|
924
|
-
* which binding fills which slot.
|
|
969
|
+
* which binding fills which slot. On a `levoParams` page it names one pattern variable
|
|
970
|
+
* (`"packages"` for `{$packages}-in-{$cities}`), which is how the platform pairs each variable
|
|
971
|
+
* with a collection. Round-tripped through the editor binding like the rest.
|
|
925
972
|
*
|
|
926
973
|
* Optional only for the pages written before it existed — see {@link recordSlug}.
|
|
927
974
|
*/
|
|
@@ -1755,6 +1802,8 @@ export declare const LevoEventRegister: ({ eventID, title, external, buttonText,
|
|
|
1755
1802
|
*/
|
|
1756
1803
|
export declare const LevoForm: React.FC<ILevoFormProps>;
|
|
1757
1804
|
|
|
1805
|
+
export declare const levoParams: <TSegment extends string, TPattern extends string, TSiblings extends string = never>(key: TSegment, pattern: TPattern, _siblings?: readonly TSiblings[]) => ILevoParams<TSegment, TPattern, TSiblings>;
|
|
1806
|
+
|
|
1758
1807
|
/**
|
|
1759
1808
|
* Mounts the site's popups, wired from `site.json`. Rendered once from the scaffold's
|
|
1760
1809
|
* `__root` (via the `SitePopups` shim), inside `<LevoSiteProvider>` — `PopupLoader` reads
|
|
@@ -1936,6 +1985,12 @@ export declare const parseRawHead: (raw: string) => IComposedHead;
|
|
|
1936
1985
|
* source resolves to null so it doesn't blank the whole page — the hook surfaces that
|
|
1937
1986
|
* binding's error while the rest render.
|
|
1938
1987
|
*
|
|
1988
|
+
* One failure is NOT absorbed outside production: a record binding whose `param` names no
|
|
1989
|
+
* param of the route. That is a wiring mistake in the page (never a transient fetch error),
|
|
1990
|
+
* it renders as a not-found record that no one can tell from a bad slug, and the preview is
|
|
1991
|
+
* where it gets fixed — so there the loader rejects with the message and the route's error
|
|
1992
|
+
* boundary shows it. See the branch below for what "preview" can mean here.
|
|
1993
|
+
*
|
|
1939
1994
|
* Returns the fetched values keyed by source key (records as the record object, lists as
|
|
1940
1995
|
* `{data, meta}`). The route loader returns this map, making it the route's `loaderData` —
|
|
1941
1996
|
* which `levoHead`'s dynamic config reads for per-record titles/descriptions/share images.
|