diff --git a/.changeset/ai-docs-audit.md b/.changeset/ai-docs-audit.md deleted file mode 100644 index 810b126..0000000 --- a/.changeset/ai-docs-audit.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -'@amritk/lynx-deep-linking': patch -'@amritk/lynx-notifications': patch -'@amritk/lynx-dialogs': patch -'@amritk/lynx-location': patch -'@amritk/mini-lynx': patch -'@amritk/mini': patch ---- - -Bring every package's shipped `AI.md` back in line with what that package -actually publishes, and add `bun run check:ai-docs` so it cannot drift again. - -The files had gone stale in the way generated-and-committed docs always do — -silently, and only for the audience that cannot file an issue about it. -`@amritk/mini` never documented `watch`, `template`, the typed `matchRoute` / -`buildPath` re-exports on `/router`, `Field` on `/forms`, or the `/vite` subpath -at all; `@amritk/mini-lynx` was missing `computed` / `effectScope`, -`fadeTransition`, `keepAboveKeyboard` and `HANDLER_PREFIX`; -`@amritk/lynx-notifications` documented neither its `/testing` subpath nor the -fake behind it. All four native packages exported `MODULE` and `EVENTS` with no -mention of what they are for, and only `@amritk/lynx-dialogs` showed how to wire -a fake into `installNativeBridge` — which is the one thing a consumer testing -its own screens needs. - -Two accuracy fixes matter more than the additions. Every native package's -*Status* section claimed the Objective-C compiles against the real Lynx pod; the -macOS CI job was disabled on cost, so it now compiles only when somebody runs -`pod lib lint` by hand, and the docs say that. And `@amritk/lynx-dialogs` never -carried a *Status* section at all, so nothing in it told a reader that none of -it has run on a device. - -`bun run check:ai-docs` reads each package's `exports` and fails on a runtime -export, a published subpath, or (for a package shipping native sources) a -*Status* section its `AI.md` never mentions. It runs early in CI, before the -build. Exports no consumer ever writes — the tree operations the JSX transform -calls, and the like — are listed in `INTERNAL_EXPORTS` with the reason. diff --git a/.changeset/ios-ci-disabled.md b/.changeset/ios-ci-disabled.md deleted file mode 100644 index 3459b27..0000000 --- a/.changeset/ios-ci-disabled.md +++ /dev/null @@ -1,14 +0,0 @@ ---- ---- - -Comment out the iOS native CI job. Four `pod lib lint` invocations ran -sequentially on a macOS runner and took 81 minutes a run — against 2m17s for the -Android equivalent — because each lint builds the Lynx engine from scratch in -its own sandbox to check roughly 2,600 lines of Objective-C. - -Nothing compiles the Objective-C now. `native-contract.test.ts` still pins the -native method surfaces against the TypeScript, so a renamed or dropped method is -still caught, but a syntax error or a missing header will reach a tarball. Run -`pod lib lint` by hand on a Mac before releasing a change under -`packages/lynx-*/ios/`. The job is left commented out in the workflow with the -two fixes worth making before turning it back on. diff --git a/.changeset/lucky-donkeys-shave.md b/.changeset/lucky-donkeys-shave.md deleted file mode 100644 index 0b67e4d..0000000 --- a/.changeset/lucky-donkeys-shave.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -'@amritk/lynx-location': minor ---- - -Add `@amritk/lynx-location` — device location for Lynx. - -Lynx ships no location module, Sparkling's built-ins stop at navigation, -storage and media, and the one published community package -(`@sigx/lynx-location`) calls `NativeModules` directly from its JavaScript half, -which is background-thread only — so a main-thread `@amritk/mini-lynx` component -importing it gets `undefined`. This is an Android module, an iOS module, and a -promise-shaped facade over `@amritk/mini-lynx-native`, built from the shape -`@amritk/lynx-notifications` established. - -`getPermissionStatus`, `requestPermission`, `getCurrentPosition`, -`getLastKnownPosition`, `isLocationEnabled`, `isLocationAvailable` and -`watchPosition`. `CLLocationManager` on iOS, `LocationManager` on Android — not -the fused provider, so no Play Services dependency and no non-GMS devices -excluded. Foreground only, deliberately: no `ACCESS_BACKGROUND_LOCATION` and no -`Always` authorisation. - -Failures are values rather than rejections — `{ ok: true, position }` or -`{ ok: false, error, message }` — because Lynx has no error convention for -bridge callbacks and a missing permission is an ordinary UI branch. - -`@amritk/lynx-location/testing` ships the native contract as an executable fake. -Nothing here has run on a device; see the package's `AGENTS.md` for what the -three CI checks do and do not cover. diff --git a/.changeset/lynx-keyboard-avoidance.md b/.changeset/lynx-keyboard-avoidance.md deleted file mode 100644 index 6c0026f..0000000 --- a/.changeset/lynx-keyboard-avoidance.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -'@amritk/mini-lynx': minor ---- - -Add `@amritk/mini-lynx/keyboard` — the soft keyboard as a signal, and the -layouts that move out of its way. - -Lynx does not avoid the keyboard for you. `` does not do it, the docs say -so outright, and what the engine offers is a single global event — -`keyboardstatuschanged`, carrying `'on' | 'off'` and a height. Every Lynx app -with a form writes the layout on top of that event, and the three mistakes are -always the same: lifting by the whole keyboard rather than by the overlap, -adding the bottom safe-area inset on top of a keyboard that already covers it, -and clearing on `blur` — which makes the screen flinch when focus moves between -two adjacent fields. - -The wiring is two lines and no provider: - -```tsx -trackKeyboard() // once, next to setEngine - - - - - -``` - -`ref` is this runtime's element-extension seam, so a control reports its own -focus and the container reads it — nothing is threaded between them. - -- **`trackKeyboard(options?)`** subscribes to the engine's `GlobalEventEmitter` - and feeds `keyboardHeight()`. The emitter is an option because Lynx's own - compatibility data lists `keyboardstatuschanged` as **unsupported on the web**: - a DOM build reports the keyboard from `visualViewport` and passes it in, and - everything downstream is the same code. -- **`keyboardLift({ inset, offset })`** is `max(0, height - inset) + offset` - while open and exactly zero while closed — the offset included, because a gap - above a keyboard that is not there is a hole in the layout. -- **`keepAboveKeyboard()`** measures the focused field against a bounds element - and answers how far a container has to rise, feeding the rise already applied - back into the next measurement so moving between fields cannot drop the - container back to rest. Both rects come from one coordinate space, so nothing - here needs pixel ratios or a status-bar height — which is the part `lynx-ui` - pays for with an `androidStatusBarPlusBottomBarHeight` prop. -- **``** is that wired to a style binding, with - `behavior='translate'` (rise by the measured overlap) or `'padding'` (reserve - the keyboard's height for a scroller). It writes only the declaration it owns - and imposes no layout of its own, and it honours `reducedMotion()` the way - `RouteStack` does. - -`/keyboard` is opt-in and its own module graph, reaching sideways into exactly -one file — `elements/invoke.ts`, for the rect — which `import-boundary.test.ts` -now pins as an exact list. - -The playground gains a `/keyboard` screen, a `visualViewport` emitter, and -`__InvokeUIMethod` on its DOM Element PAPI (`boundingClientRect` and -`scrollIntoView` answered honestly, everything else reported as unimplemented -rather than invented). - -**Not verified on a device.** The arithmetic and the components are covered -against the fake engine, but the engine event itself, the units it reports, and -how a real IME animation interacts with the transition are unconfirmed on -hardware. diff --git a/.changeset/lynx-location-reverse-geocoding.md b/.changeset/lynx-location-reverse-geocoding.md deleted file mode 100644 index 07a635c..0000000 --- a/.changeset/lynx-location-reverse-geocoding.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'@amritk/lynx-location': minor ---- - -Add `reverseGeocode` to `@amritk/lynx-location` — coordinates to a postal -address, on both platforms. - -`CLGeocoder` on iOS, `android.location.Geocoder` on Android. The Android half -uses the API 33 callback form where it exists and the blocking form on an -executor below that, because this library supports devices well under 33 and a -network round trip on the calling thread is an ANR waiting to happen. - -**It needs no permission and never prompts.** Reverse geocoding reads no device -location — it geocodes the coordinates it is handed — so an app that has been -refused location outright can still label a saved venue or a map centre. A -`LocationFix` satisfies the new `Coordinates` type, so pairing it with -`getCurrentPosition` needs no mapping step. - -`GeocodeResult` is a discriminated union like the rest of the package: -`{ ok: true, addresses }` or `{ ok: false, error, message }`, where `error` is -`invalidCoordinates`, `notFound`, `network` or `unavailable`. Being throttled is -`network` rather than a code of its own — Apple rate-limits `CLGeocoder` and -reports it that way, and Android has no equivalent to report. `notFound` is the -only spelling of "there is no address there"; an empty `addresses` is never a -success. - -Every `GeocodeAddress` field is nullable, and `formattedAddress` is built by the -OS — `getAddressLine(0)` on Android, `CNPostalAddressFormatter` on iOS — so it -places each country's postcode where that country places it. `isoCountryCode` is -the only field stable across locales. - -The iOS half now links `Contacts`, for `CNPostalAddressFormatter` alone. It -reaches no contact store and needs no permission. - -`createFakeLocation` gains `setNextAddresses`, `setGeocoderPresent`, -`setNetworkAvailable` and `geocodes()`. Nothing here has run on a device; the -package's `AGENTS.md` lists what reverse geocoding specifically leaves unproven. diff --git a/.changeset/lynx-native-dialogs.md b/.changeset/lynx-native-dialogs.md deleted file mode 100644 index 8c92ba6..0000000 --- a/.changeset/lynx-native-dialogs.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -'@amritk/lynx-dialogs': minor ---- - -Add `@amritk/lynx-dialogs` — the platform's own date picker, action sheet and -alert for Lynx. - -Lynx ships no picker element and no modal module, and neither published option -fills the gap: `@lynx-js/lynx-ui-sheet` draws a sheet out of ReactLynx elements -rather than presenting a native one, and `@sigx/lynx-datetime-picker` is a real -`UIDatePicker`/`DatePickerDialog` bolted to another framework's bridge. Nothing -native exists for action sheets at all. - -So this is an Android module, an iOS module, and a promise-shaped facade over -`@amritk/mini-lynx-native`: - -```ts -const date = await presentDatePicker({ mode: 'date', maximum: Date.now() }) -if (date.ok) setDeparture(new Date(date.value)) - -const choice = await presentActionSheet({ - actions: [{ label: 'Replace' }, { label: 'Delete', destructive: true }], -}) -if (choice.ok) apply(choice.index) - -const confirm = await presentAlert({ - title: 'Delete this photo?', - buttons: [{ label: 'Cancel', style: 'cancel' }, { label: 'Delete', style: 'destructive' }], -}) -if (confirm.ok && confirm.index === 1) remove() -``` - -`presentDatePicker` covers `date`, `time` and `datetime` with bounds, labels and -a 12/24-hour override; `presentActionSheet` covers destructive and disabled rows -and the iPad popover anchor; `presentAlert` covers one to three buttons with -cancel and destructive styling. `dismissActiveDialog` closes whatever is up so a -screen can clean up on navigation, and `areDialogsAvailable` reports whether the -host app linked the module. - -`AlertButtons` is a one-to-three tuple rather than an array, because -`AlertDialog` has exactly three button slots and there is no fourth — so the -Android cap is a compile error instead of a button that goes missing on half the -devices an app runs on. - -Every outcome is a discriminated union rather than a rejection — cancelling is -the most likely thing a user does with a dialog, so it is a branch. Only one -presentation is allowed on screen at a time; a second resolves -`{ ok: false, reason: 'busy' }` rather than stacking on Android or hanging -forever on iOS. - -Unlike the other two native packages there is no permission, no `Info.plist` key -and no manifest entry for the host app to add, and the Android half takes no -dependencies at all — the framework `AlertDialog` rather than Material, so no -`Theme.Material3` requirement lands on the host's Activity. - -`@amritk/lynx-dialogs/testing` ships the native contract as an executable fake. diff --git a/.changeset/lynx-push-notifications.md b/.changeset/lynx-push-notifications.md deleted file mode 100644 index 74a126e..0000000 --- a/.changeset/lynx-push-notifications.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -'@amritk/mini-lynx-native': minor -'@amritk/lynx-notifications': minor ---- - -Add native modules for Lynx, starting with push notifications. - -Lynx ships no notifications module, and Sparkling's `sparkling-notifications` is -a reserved npm name with no implementation behind it — so the official answer is -still "write native code and send it into your Lynx code". These two packages -are that, plus the piece it needs first. - -**`@amritk/mini-lynx-native`** is the wire. Lynx's `NativeModules` and -`GlobalEventEmitter` are background-thread globals, while `@amritk/mini-lynx` -renders on the main thread because the Element PAPI is a main-thread API — so a -component reaching for a native module finds `undefined`, with nothing to read. -The package carries calls one way and events the other: `callNative` and -`callNativeAsync` for the two shapes a Lynx native method comes in, -`isNativeModuleAvailable` for feature detection, and `onNativeEvent` for -`sendGlobalEvent`. Calls made before the background half is installed are queued -rather than lost, because the main-thread chunk usually runs first. The -`/background` subpath is the half an app installs in its background chunk, in -one line; `/testing` ships the fakes both halves run against. - -**`@amritk/lynx-notifications`** is the first module built on it: local and -remote push, with an Android implementation (`NotificationManager`, -`AlarmManager`, FCM) and an iOS one (`UNUserNotificationCenter`, APNs) declared -to Lynx's autolinker through `lynx.lib.json`. The JavaScript surface is promises -and subscriptions rather than signals, deliberately — a second edge onto the -signal engine is how a consumer ends up with two reactive graphs that cannot see -each other's writes. - -Both native halves are compiled in CI — Gradle against the real -`org.lynxsdk.lynx:lynx` AAR for Android, `pod lib lint` against the real Lynx pod -on a macOS runner for iOS — and `src/native-contract.test.ts` pins their method -names, arities and event strings against the TypeScript, which is the one class -of drift no compiler on either side can catch. `bun run check:android` runs the -Android compile locally and skips with an explanation when there is no SDK. - -**None of that has run on a device.** Permission flows, `AlarmManager` under -Doze, APNs registration and FCM delivery are unverified. The caveat is carried in -the package's `README.md`, `AI.md` and `AGENTS.md`. diff --git a/.changeset/olive-pots-repeat.md b/.changeset/olive-pots-repeat.md deleted file mode 100644 index c842a83..0000000 --- a/.changeset/olive-pots-repeat.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@amritk/lynx-notifications': patch ---- - -Backport the dangling-selector check from `@amritk/lynx-location`'s parity -suite: `native-contract.test.ts` now asserts that every selector named in the -Objective-C `methodLookup` table has a method implementing it. - -That is the one cross-language failure nothing else here could see. A selector -string pointing at no method is not a build error on iOS — `pod lib lint` -passes — and fails only when Lynx tries to dispatch through it, on a device, as -a promise that never settles. All eleven of the package's selectors resolve -today; the check is mutation-verified. - -No runtime change. diff --git a/.changeset/playground-native-modules.md b/.changeset/playground-native-modules.md deleted file mode 100644 index 3e7239b..0000000 --- a/.changeset/playground-native-modules.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -'@amritk/mini-lynx-native': patch ---- - -Correct the two mis-call failure modes in `AI.md`, and pin them with tests. - -The gotchas list had them the wrong way round: it said `callNative` on a -callback method never settles and `callNativeAsync` on a returning one gives -you `undefined`. The bridge does the opposite, and it is not a close call — -the `return` form always replies, so `callNative` always settles, while the -`callback` form appends a callback that a returning method ignores, so nothing -ever replies at all. - -```ts -// settles: with `undefined` for a method that stores the callback, or as a -// rejection when the method reaches for the argument it was not handed -await callNative('StorageModule', 'loadValue', 'profile') - -// never settles: the appended callback is an argument the method ignores -await callNativeAsync('StorageModule', 'getValue', 'token') -``` - -Both are now cases in `channel.test.ts`, because the symmetric-sounding summary -is the half that sends people looking in the wrong place — a promise that never -settles reads as a thread problem, and it is a one-word choice at the call site. - -Found by wiring the package into `apps/playground-mini-lynx`, which now has a -screen per native package driving them through each package's own published -fake. diff --git a/.changeset/quick-pans-invent.md b/.changeset/quick-pans-invent.md deleted file mode 100644 index 1379701..0000000 --- a/.changeset/quick-pans-invent.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -'@amritk/mini-lynx': patch ---- - -Cut main-thread work out of the paths every screen runs. - -This runtime is main-thread because the Element PAPI is, so the work it does -building and updating a tree is not work a background thread can absorb — it is -work the frame has to fit around. Five changes, all in the hot paths, none of -them changing an API: - -- `jsx-runtime.ts` walks props with `for…in` instead of `Object.entries`, which - was allocating an array plus a pair per prop, per element. It was the single - hottest function in a CPU profile of building a thousand-row list. -- `apply-prop.ts` remembers the event-prefix parse per prop name. Every prop - used to pay up to six `startsWith` scans on its way to `__SetAttribute`; a - given spelling is now scanned once for the life of the app. -- `style/to-css-name.ts` and `style/to-style-text.ts` do the same for the CSS - spelling of a style key and the unitless verdict on it. Both ran a regex per - key per write, which for a reactive style bag — `style={() => ({ paddingBottom: - keyboardHeight() })}` — meant rediscovering the same answers every frame. -- `add-event.ts` no longer copies a handler set of one on every delivered event, - and builds that set empty rather than from an iterable it had to allocate. The - dispatcher runs on every frame of a scroll, where its garbage competes with - the layout it is scrolling. -- `resolve-class.ts` collects into one accumulator instead of - `map`/`filter`/`join`, which allocated three arrays per array level and four - per toggle map to produce one string. A reactive `class` is the binding an app - re-runs most. - -Measured against an engine that does nothing but keep the tree — so the number -is the runtime's own cost and not a host's — building a thousand rows went from -~9.5ms to ~7ms, about a quarter. - -Separately, `testing/create-fake-engine.ts` trims its call log in batches -instead of on every call. It was doing `calls.slice(-1000)` per PAPI call once -past the first thousand, so a suite that builds a thousand rows paid twenty-five -million array writes for a log it keeps a thousand lines of — which also meant -the package's own reconciler benchmark was mostly measuring the fake. `calls()` -slices on the way out, so the window a caller sees is unchanged. End to end that -takes `create 10,000 rows` in `bun run bench:reconciler` from ~840ms to ~170ms, -and `create 1,000 rows` from ~105ms to ~20ms, with the engine-call counts -identical. - -The core's gzipped byte budget moves 5449 → 5559 to pay for the lookup tables. -See the note in `core-size-budget.test.ts` for the reasoning; it is the third -deliberate move. diff --git a/.changeset/rotten-buttons-grin.md b/.changeset/rotten-buttons-grin.md deleted file mode 100644 index c956370..0000000 --- a/.changeset/rotten-buttons-grin.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -'@amritk/lynx-deep-linking': minor ---- - -Add `@amritk/lynx-deep-linking` — deep links for Lynx, as an Android native -module, an iOS native module and a promise-shaped facade over -`@amritk/mini-lynx-native`. - -Lynx ships no linking module, and the two published community packages are parts -of frameworks rather than libraries: `@sigx/lynx-linking` needs `@sigx/lynx-core` -and a `sigx prebuild` step, `@tamer4lynx/tamer-linking` peers on ReactLynx and -links through the `t4l` CLI, and neither reaches the main thread a `mini-lynx` -tree renders on. - -Inbound, the launch URL is a value (`getInitialURL`) and every later link is an -event (`onDeepLink`), so an app can handle both without handling one tap twice; -a link that arrives with no view up is held natively and replayed once. Outbound, -`openURL`, `canOpenURL` and `openSettings` — the last being the missing half of -the `denied` states `@amritk/lynx-location` and `@amritk/lynx-notifications` -report. `parseURL` and `createURL` are pure and need no bridge. - -Cold start needs no host wiring on either platform; a link into a running app -needs one forwarded callback, which no library can observe for itself. diff --git a/.changeset/typed-routes-and-browser-history.md b/.changeset/typed-routes-and-browser-history.md deleted file mode 100644 index 02ea991..0000000 --- a/.changeset/typed-routes-and-browser-history.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -'@amritk/mini-helpers': minor -'@amritk/mini-lynx': minor -'@amritk/mini': minor ---- - -Type route params from the pattern, and give `@amritk/mini-lynx` a browser -history again. - -**The pattern is now read at the type level.** `PathParams<'/users/:id'>` is -`{ id: string }`, and `matchRoute` is generic over its pattern, so -`matchRoute('/users/:id', path)` hands back `{ id: string } | null` instead of a -record that answers `string` for every key including the misspelt ones. It lives -in `@amritk/mini-helpers` beside the matcher it mirrors, because the grammar has -to be one definition or the value and type worlds drift; `path-params.test.ts` -pins the two together, down to `/v:major` being a literal segment on both sides. - -`PathParams` is `RouteParams`, so this is additive: a table annotated -`Route[]`, or built at runtime, compiles exactly as before and gets exactly what -it got before. - -**`buildPath` is the inverse.** `buildPath('/users/:id', { id })` cannot spell -the pattern wrong, forget a param or be left behind when a route is renamed, and -its values are encoded to round-trip back through `matchRoute`. A `*` wildcard -is encoded per segment, because `rest` is a path and its slashes are structure. -`navigate` still takes a plain string, deliberately — a router navigates to -concrete paths, and one that matches nothing is what a fallback screen is for — -so the check sits where the string is assembled instead. - -**`@amritk/mini-lynx/router` gains `route()` and `AnyRoute`.** -`route('/users/:id', (params) => …, meta)` keeps the pattern's literal type -alive so the view is handed a `() => { id: string }`. Its three arguments are -not a style choice: folded into one object literal, the pattern and the metadata -compete for inference against an intersection, and the metadata loses — a tab -bar reading `route.label` would get `unknown` back. - -`Route` is now `Route

`, and the router's generics are -constrained by the new `AnyRoute`. `Route<'/users/:id'>` is deliberately not -assignable to `Route` — its `view` demands the narrow getter, and the -widened form can only promise the flat record — so a table of mixed patterns -needs a constraint that admits all of them. `render-route.ts` owns the single -cast that reconciles the two. Existing tables keep working: `Route` with no type -argument is what it always was. - -**`createBrowserHistory` is back, on `@amritk/mini-lynx/router/browser`.** It is -a `RouterHistory` and nothing else, because the rest of routing is already -target-free — a device build swaps `createMemoryHistory()` back in and changes -nothing else. It takes a `base` prefix, and it stamps its depth into -`history.state` rather than reading `window.history.length`, which counts the -whole tab and is wrong in both directions once the user has gone back. That -stamp is what survives the two things a memory stack never faces: a reload -mid-stack and a forward button. - -It is the only module in the package that names `window`, and it is quarantined -accordingly. `src/router/browser/` is excluded from the main compiler pass and -covered by `tsconfig.dom.json`, so the platform-free rule the rest of the -package keeps stays something the compiler enforces rather than a convention. -Hash mode is not included: configuring a host for an SPA fallback is one line, -where a second URL grammar would be one the device build can never use. - -**`stripBase` moved into `@amritk/mini-helpers`,** since both routers now read a -browser pathname and a base that meant something slightly different on each side -is exactly the drift that package exists to prevent. `@amritk/mini`'s router -surface is unchanged apart from re-exporting `buildPath` and `PathParams`. diff --git a/apps/playground-mini-lynx/CHANGELOG.md b/apps/playground-mini-lynx/CHANGELOG.md index ac216d3..aa405a7 100644 --- a/apps/playground-mini-lynx/CHANGELOG.md +++ b/apps/playground-mini-lynx/CHANGELOG.md @@ -1,5 +1,27 @@ # @amritk/playground-mini-lynx +## 0.0.4 + +### Patch Changes + +- Updated dependencies [1b6c33d] +- Updated dependencies [293234c] +- Updated dependencies [59983c4] +- Updated dependencies [7c690ed] +- Updated dependencies [2b07688] +- Updated dependencies [e025ac7] +- Updated dependencies [4423822] +- Updated dependencies [5101aa7] +- Updated dependencies [d11610b] +- Updated dependencies [c657444] +- Updated dependencies [ab6476a] + - @amritk/lynx-deep-linking@0.2.0 + - @amritk/lynx-notifications@0.2.0 + - @amritk/lynx-dialogs@0.2.0 + - @amritk/lynx-location@0.2.0 + - @amritk/mini-lynx@0.4.0 + - @amritk/mini-lynx-native@0.2.0 + ## 0.0.3 ### Patch Changes diff --git a/apps/playground-mini-lynx/package.json b/apps/playground-mini-lynx/package.json index 3664d80..10457cd 100644 --- a/apps/playground-mini-lynx/package.json +++ b/apps/playground-mini-lynx/package.json @@ -1,6 +1,6 @@ { "name": "@amritk/playground-mini-lynx", - "version": "0.0.3", + "version": "0.0.4", "private": true, "description": "Kitchen-sink playground for @amritk/mini-lynx and the native modules around it, previewed through a DOM implementation of Lynx's Element PAPI and deployed to Cloudflare Workers as a static SPA.", "type": "module", diff --git a/apps/playground-mini/CHANGELOG.md b/apps/playground-mini/CHANGELOG.md index e32aeaf..d522560 100644 --- a/apps/playground-mini/CHANGELOG.md +++ b/apps/playground-mini/CHANGELOG.md @@ -1,5 +1,13 @@ # @amritk/playground-mini +## 0.0.2 + +### Patch Changes + +- Updated dependencies [1b6c33d] +- Updated dependencies [ab6476a] + - @amritk/mini@0.7.0 + ## 0.0.1 ### Patch Changes diff --git a/apps/playground-mini/package.json b/apps/playground-mini/package.json index c821c0a..a0d74df 100644 --- a/apps/playground-mini/package.json +++ b/apps/playground-mini/package.json @@ -1,6 +1,6 @@ { "name": "@amritk/playground-mini", - "version": "0.0.1", + "version": "0.0.2", "private": true, "description": "Kitchen-sink playground for @amritk/mini, deployed to Cloudflare Workers as a static SPA.", "type": "module", diff --git a/packages/lynx-deep-linking/CHANGELOG.md b/packages/lynx-deep-linking/CHANGELOG.md new file mode 100644 index 0000000..5d953e6 --- /dev/null +++ b/packages/lynx-deep-linking/CHANGELOG.md @@ -0,0 +1,61 @@ +# @amritk/lynx-deep-linking + +## 0.2.0 + +### Minor Changes + +- c657444: Add `@amritk/lynx-deep-linking` — deep links for Lynx, as an Android native + module, an iOS native module and a promise-shaped facade over + `@amritk/mini-lynx-native`. + + Lynx ships no linking module, and the two published community packages are parts + of frameworks rather than libraries: `@sigx/lynx-linking` needs `@sigx/lynx-core` + and a `sigx prebuild` step, `@tamer4lynx/tamer-linking` peers on ReactLynx and + links through the `t4l` CLI, and neither reaches the main thread a `mini-lynx` + tree renders on. + + Inbound, the launch URL is a value (`getInitialURL`) and every later link is an + event (`onDeepLink`), so an app can handle both without handling one tap twice; + a link that arrives with no view up is held natively and replayed once. Outbound, + `openURL`, `canOpenURL` and `openSettings` — the last being the missing half of + the `denied` states `@amritk/lynx-location` and `@amritk/lynx-notifications` + report. `parseURL` and `createURL` are pure and need no bridge. + + Cold start needs no host wiring on either platform; a link into a running app + needs one forwarded callback, which no library can observe for itself. + +### Patch Changes + +- 1b6c33d: Bring every package's shipped `AI.md` back in line with what that package + actually publishes, and add `bun run check:ai-docs` so it cannot drift again. + + The files had gone stale in the way generated-and-committed docs always do — + silently, and only for the audience that cannot file an issue about it. + `@amritk/mini` never documented `watch`, `template`, the typed `matchRoute` / + `buildPath` re-exports on `/router`, `Field` on `/forms`, or the `/vite` subpath + at all; `@amritk/mini-lynx` was missing `computed` / `effectScope`, + `fadeTransition`, `keepAboveKeyboard` and `HANDLER_PREFIX`; + `@amritk/lynx-notifications` documented neither its `/testing` subpath nor the + fake behind it. All four native packages exported `MODULE` and `EVENTS` with no + mention of what they are for, and only `@amritk/lynx-dialogs` showed how to wire + a fake into `installNativeBridge` — which is the one thing a consumer testing + its own screens needs. + + Two accuracy fixes matter more than the additions. Every native package's + _Status_ section claimed the Objective-C compiles against the real Lynx pod; the + macOS CI job was disabled on cost, so it now compiles only when somebody runs + `pod lib lint` by hand, and the docs say that. And `@amritk/lynx-dialogs` never + carried a _Status_ section at all, so nothing in it told a reader that none of + it has run on a device. + + `bun run check:ai-docs` reads each package's `exports` and fails on a runtime + export, a published subpath, or (for a package shipping native sources) a + _Status_ section its `AI.md` never mentions. It runs early in CI, before the + build. Exports no consumer ever writes — the tree operations the JSX transform + calls, and the like — are listed in `INTERNAL_EXPORTS` with the reason. + +- Updated dependencies [e025ac7] +- Updated dependencies [5101aa7] +- Updated dependencies [ab6476a] + - @amritk/mini-lynx-native@0.2.0 + - @amritk/mini-helpers@0.2.0 diff --git a/packages/lynx-deep-linking/package.json b/packages/lynx-deep-linking/package.json index 8053957..fd0629c 100644 --- a/packages/lynx-deep-linking/package.json +++ b/packages/lynx-deep-linking/package.json @@ -1,6 +1,6 @@ { "name": "@amritk/lynx-deep-linking", - "version": "0.1.0", + "version": "0.2.0", "description": "Deep links for Lynx: an Android and iOS native module, and a promise-shaped facade that reaches it from the main thread.", "type": "module", "license": "MIT", diff --git a/packages/lynx-dialogs/CHANGELOG.md b/packages/lynx-dialogs/CHANGELOG.md new file mode 100644 index 0000000..35584fe --- /dev/null +++ b/packages/lynx-dialogs/CHANGELOG.md @@ -0,0 +1,95 @@ +# @amritk/lynx-dialogs + +## 0.2.0 + +### Minor Changes + +- 2b07688: Add `@amritk/lynx-dialogs` — the platform's own date picker, action sheet and + alert for Lynx. + + Lynx ships no picker element and no modal module, and neither published option + fills the gap: `@lynx-js/lynx-ui-sheet` draws a sheet out of ReactLynx elements + rather than presenting a native one, and `@sigx/lynx-datetime-picker` is a real + `UIDatePicker`/`DatePickerDialog` bolted to another framework's bridge. Nothing + native exists for action sheets at all. + + So this is an Android module, an iOS module, and a promise-shaped facade over + `@amritk/mini-lynx-native`: + + ```ts + const date = await presentDatePicker({ mode: "date", maximum: Date.now() }); + if (date.ok) setDeparture(new Date(date.value)); + + const choice = await presentActionSheet({ + actions: [{ label: "Replace" }, { label: "Delete", destructive: true }], + }); + if (choice.ok) apply(choice.index); + + const confirm = await presentAlert({ + title: "Delete this photo?", + buttons: [ + { label: "Cancel", style: "cancel" }, + { label: "Delete", style: "destructive" }, + ], + }); + if (confirm.ok && confirm.index === 1) remove(); + ``` + + `presentDatePicker` covers `date`, `time` and `datetime` with bounds, labels and + a 12/24-hour override; `presentActionSheet` covers destructive and disabled rows + and the iPad popover anchor; `presentAlert` covers one to three buttons with + cancel and destructive styling. `dismissActiveDialog` closes whatever is up so a + screen can clean up on navigation, and `areDialogsAvailable` reports whether the + host app linked the module. + + `AlertButtons` is a one-to-three tuple rather than an array, because + `AlertDialog` has exactly three button slots and there is no fourth — so the + Android cap is a compile error instead of a button that goes missing on half the + devices an app runs on. + + Every outcome is a discriminated union rather than a rejection — cancelling is + the most likely thing a user does with a dialog, so it is a branch. Only one + presentation is allowed on screen at a time; a second resolves + `{ ok: false, reason: 'busy' }` rather than stacking on Android or hanging + forever on iOS. + + Unlike the other two native packages there is no permission, no `Info.plist` key + and no manifest entry for the host app to add, and the Android half takes no + dependencies at all — the framework `AlertDialog` rather than Material, so no + `Theme.Material3` requirement lands on the host's Activity. + + `@amritk/lynx-dialogs/testing` ships the native contract as an executable fake. + +### Patch Changes + +- 1b6c33d: Bring every package's shipped `AI.md` back in line with what that package + actually publishes, and add `bun run check:ai-docs` so it cannot drift again. + + The files had gone stale in the way generated-and-committed docs always do — + silently, and only for the audience that cannot file an issue about it. + `@amritk/mini` never documented `watch`, `template`, the typed `matchRoute` / + `buildPath` re-exports on `/router`, `Field` on `/forms`, or the `/vite` subpath + at all; `@amritk/mini-lynx` was missing `computed` / `effectScope`, + `fadeTransition`, `keepAboveKeyboard` and `HANDLER_PREFIX`; + `@amritk/lynx-notifications` documented neither its `/testing` subpath nor the + fake behind it. All four native packages exported `MODULE` and `EVENTS` with no + mention of what they are for, and only `@amritk/lynx-dialogs` showed how to wire + a fake into `installNativeBridge` — which is the one thing a consumer testing + its own screens needs. + + Two accuracy fixes matter more than the additions. Every native package's + _Status_ section claimed the Objective-C compiles against the real Lynx pod; the + macOS CI job was disabled on cost, so it now compiles only when somebody runs + `pod lib lint` by hand, and the docs say that. And `@amritk/lynx-dialogs` never + carried a _Status_ section at all, so nothing in it told a reader that none of + it has run on a device. + + `bun run check:ai-docs` reads each package's `exports` and fails on a runtime + export, a published subpath, or (for a package shipping native sources) a + _Status_ section its `AI.md` never mentions. It runs early in CI, before the + build. Exports no consumer ever writes — the tree operations the JSX transform + calls, and the like — are listed in `INTERNAL_EXPORTS` with the reason. + +- Updated dependencies [e025ac7] +- Updated dependencies [5101aa7] + - @amritk/mini-lynx-native@0.2.0 diff --git a/packages/lynx-dialogs/package.json b/packages/lynx-dialogs/package.json index 356eda9..d6443d3 100644 --- a/packages/lynx-dialogs/package.json +++ b/packages/lynx-dialogs/package.json @@ -1,6 +1,6 @@ { "name": "@amritk/lynx-dialogs", - "version": "0.1.0", + "version": "0.2.0", "description": "Native date pickers and action sheets for Lynx: an Android and iOS native module, and a promise-shaped facade that reaches it from the main thread.", "type": "module", "license": "MIT", diff --git a/packages/lynx-location/CHANGELOG.md b/packages/lynx-location/CHANGELOG.md new file mode 100644 index 0000000..325b2fe --- /dev/null +++ b/packages/lynx-location/CHANGELOG.md @@ -0,0 +1,98 @@ +# @amritk/lynx-location + +## 0.2.0 + +### Minor Changes + +- 293234c: Add `@amritk/lynx-location` — device location for Lynx. + + Lynx ships no location module, Sparkling's built-ins stop at navigation, + storage and media, and the one published community package + (`@sigx/lynx-location`) calls `NativeModules` directly from its JavaScript half, + which is background-thread only — so a main-thread `@amritk/mini-lynx` component + importing it gets `undefined`. This is an Android module, an iOS module, and a + promise-shaped facade over `@amritk/mini-lynx-native`, built from the shape + `@amritk/lynx-notifications` established. + + `getPermissionStatus`, `requestPermission`, `getCurrentPosition`, + `getLastKnownPosition`, `isLocationEnabled`, `isLocationAvailable` and + `watchPosition`. `CLLocationManager` on iOS, `LocationManager` on Android — not + the fused provider, so no Play Services dependency and no non-GMS devices + excluded. Foreground only, deliberately: no `ACCESS_BACKGROUND_LOCATION` and no + `Always` authorisation. + + Failures are values rather than rejections — `{ ok: true, position }` or + `{ ok: false, error, message }` — because Lynx has no error convention for + bridge callbacks and a missing permission is an ordinary UI branch. + + `@amritk/lynx-location/testing` ships the native contract as an executable fake. + Nothing here has run on a device; see the package's `AGENTS.md` for what the + three CI checks do and do not cover. + +- 7c690ed: Add `reverseGeocode` to `@amritk/lynx-location` — coordinates to a postal + address, on both platforms. + + `CLGeocoder` on iOS, `android.location.Geocoder` on Android. The Android half + uses the API 33 callback form where it exists and the blocking form on an + executor below that, because this library supports devices well under 33 and a + network round trip on the calling thread is an ANR waiting to happen. + + **It needs no permission and never prompts.** Reverse geocoding reads no device + location — it geocodes the coordinates it is handed — so an app that has been + refused location outright can still label a saved venue or a map centre. A + `LocationFix` satisfies the new `Coordinates` type, so pairing it with + `getCurrentPosition` needs no mapping step. + + `GeocodeResult` is a discriminated union like the rest of the package: + `{ ok: true, addresses }` or `{ ok: false, error, message }`, where `error` is + `invalidCoordinates`, `notFound`, `network` or `unavailable`. Being throttled is + `network` rather than a code of its own — Apple rate-limits `CLGeocoder` and + reports it that way, and Android has no equivalent to report. `notFound` is the + only spelling of "there is no address there"; an empty `addresses` is never a + success. + + Every `GeocodeAddress` field is nullable, and `formattedAddress` is built by the + OS — `getAddressLine(0)` on Android, `CNPostalAddressFormatter` on iOS — so it + places each country's postcode where that country places it. `isoCountryCode` is + the only field stable across locales. + + The iOS half now links `Contacts`, for `CNPostalAddressFormatter` alone. It + reaches no contact store and needs no permission. + + `createFakeLocation` gains `setNextAddresses`, `setGeocoderPresent`, + `setNetworkAvailable` and `geocodes()`. Nothing here has run on a device; the + package's `AGENTS.md` lists what reverse geocoding specifically leaves unproven. + +### Patch Changes + +- 1b6c33d: Bring every package's shipped `AI.md` back in line with what that package + actually publishes, and add `bun run check:ai-docs` so it cannot drift again. + + The files had gone stale in the way generated-and-committed docs always do — + silently, and only for the audience that cannot file an issue about it. + `@amritk/mini` never documented `watch`, `template`, the typed `matchRoute` / + `buildPath` re-exports on `/router`, `Field` on `/forms`, or the `/vite` subpath + at all; `@amritk/mini-lynx` was missing `computed` / `effectScope`, + `fadeTransition`, `keepAboveKeyboard` and `HANDLER_PREFIX`; + `@amritk/lynx-notifications` documented neither its `/testing` subpath nor the + fake behind it. All four native packages exported `MODULE` and `EVENTS` with no + mention of what they are for, and only `@amritk/lynx-dialogs` showed how to wire + a fake into `installNativeBridge` — which is the one thing a consumer testing + its own screens needs. + + Two accuracy fixes matter more than the additions. Every native package's + _Status_ section claimed the Objective-C compiles against the real Lynx pod; the + macOS CI job was disabled on cost, so it now compiles only when somebody runs + `pod lib lint` by hand, and the docs say that. And `@amritk/lynx-dialogs` never + carried a _Status_ section at all, so nothing in it told a reader that none of + it has run on a device. + + `bun run check:ai-docs` reads each package's `exports` and fails on a runtime + export, a published subpath, or (for a package shipping native sources) a + _Status_ section its `AI.md` never mentions. It runs early in CI, before the + build. Exports no consumer ever writes — the tree operations the JSX transform + calls, and the like — are listed in `INTERNAL_EXPORTS` with the reason. + +- Updated dependencies [e025ac7] +- Updated dependencies [5101aa7] + - @amritk/mini-lynx-native@0.2.0 diff --git a/packages/lynx-location/package.json b/packages/lynx-location/package.json index 0195614..d54cf5b 100644 --- a/packages/lynx-location/package.json +++ b/packages/lynx-location/package.json @@ -1,6 +1,6 @@ { "name": "@amritk/lynx-location", - "version": "0.1.0", + "version": "0.2.0", "description": "Device location for Lynx: an Android and iOS native module, and a promise-shaped facade that reaches it from the main thread.", "type": "module", "license": "MIT", diff --git a/packages/lynx-notifications/CHANGELOG.md b/packages/lynx-notifications/CHANGELOG.md new file mode 100644 index 0000000..fe0eefe --- /dev/null +++ b/packages/lynx-notifications/CHANGELOG.md @@ -0,0 +1,89 @@ +# @amritk/lynx-notifications + +## 0.2.0 + +### Minor Changes + +- e025ac7: Add native modules for Lynx, starting with push notifications. + + Lynx ships no notifications module, and Sparkling's `sparkling-notifications` is + a reserved npm name with no implementation behind it — so the official answer is + still "write native code and send it into your Lynx code". These two packages + are that, plus the piece it needs first. + + **`@amritk/mini-lynx-native`** is the wire. Lynx's `NativeModules` and + `GlobalEventEmitter` are background-thread globals, while `@amritk/mini-lynx` + renders on the main thread because the Element PAPI is a main-thread API — so a + component reaching for a native module finds `undefined`, with nothing to read. + The package carries calls one way and events the other: `callNative` and + `callNativeAsync` for the two shapes a Lynx native method comes in, + `isNativeModuleAvailable` for feature detection, and `onNativeEvent` for + `sendGlobalEvent`. Calls made before the background half is installed are queued + rather than lost, because the main-thread chunk usually runs first. The + `/background` subpath is the half an app installs in its background chunk, in + one line; `/testing` ships the fakes both halves run against. + + **`@amritk/lynx-notifications`** is the first module built on it: local and + remote push, with an Android implementation (`NotificationManager`, + `AlarmManager`, FCM) and an iOS one (`UNUserNotificationCenter`, APNs) declared + to Lynx's autolinker through `lynx.lib.json`. The JavaScript surface is promises + and subscriptions rather than signals, deliberately — a second edge onto the + signal engine is how a consumer ends up with two reactive graphs that cannot see + each other's writes. + + Both native halves are compiled in CI — Gradle against the real + `org.lynxsdk.lynx:lynx` AAR for Android, `pod lib lint` against the real Lynx pod + on a macOS runner for iOS — and `src/native-contract.test.ts` pins their method + names, arities and event strings against the TypeScript, which is the one class + of drift no compiler on either side can catch. `bun run check:android` runs the + Android compile locally and skips with an explanation when there is no SDK. + + **None of that has run on a device.** Permission flows, `AlarmManager` under + Doze, APNs registration and FCM delivery are unverified. The caveat is carried in + the package's `README.md`, `AI.md` and `AGENTS.md`. + +### Patch Changes + +- 1b6c33d: Bring every package's shipped `AI.md` back in line with what that package + actually publishes, and add `bun run check:ai-docs` so it cannot drift again. + + The files had gone stale in the way generated-and-committed docs always do — + silently, and only for the audience that cannot file an issue about it. + `@amritk/mini` never documented `watch`, `template`, the typed `matchRoute` / + `buildPath` re-exports on `/router`, `Field` on `/forms`, or the `/vite` subpath + at all; `@amritk/mini-lynx` was missing `computed` / `effectScope`, + `fadeTransition`, `keepAboveKeyboard` and `HANDLER_PREFIX`; + `@amritk/lynx-notifications` documented neither its `/testing` subpath nor the + fake behind it. All four native packages exported `MODULE` and `EVENTS` with no + mention of what they are for, and only `@amritk/lynx-dialogs` showed how to wire + a fake into `installNativeBridge` — which is the one thing a consumer testing + its own screens needs. + + Two accuracy fixes matter more than the additions. Every native package's + _Status_ section claimed the Objective-C compiles against the real Lynx pod; the + macOS CI job was disabled on cost, so it now compiles only when somebody runs + `pod lib lint` by hand, and the docs say that. And `@amritk/lynx-dialogs` never + carried a _Status_ section at all, so nothing in it told a reader that none of + it has run on a device. + + `bun run check:ai-docs` reads each package's `exports` and fails on a runtime + export, a published subpath, or (for a package shipping native sources) a + _Status_ section its `AI.md` never mentions. It runs early in CI, before the + build. Exports no consumer ever writes — the tree operations the JSX transform + calls, and the like — are listed in `INTERNAL_EXPORTS` with the reason. + +- 4423822: Backport the dangling-selector check from `@amritk/lynx-location`'s parity + suite: `native-contract.test.ts` now asserts that every selector named in the + Objective-C `methodLookup` table has a method implementing it. + + That is the one cross-language failure nothing else here could see. A selector + string pointing at no method is not a build error on iOS — `pod lib lint` + passes — and fails only when Lynx tries to dispatch through it, on a device, as + a promise that never settles. All eleven of the package's selectors resolve + today; the check is mutation-verified. + + No runtime change. + +- Updated dependencies [e025ac7] +- Updated dependencies [5101aa7] + - @amritk/mini-lynx-native@0.2.0 diff --git a/packages/lynx-notifications/package.json b/packages/lynx-notifications/package.json index 65b01da..6ff3f8b 100644 --- a/packages/lynx-notifications/package.json +++ b/packages/lynx-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@amritk/lynx-notifications", - "version": "0.1.0", + "version": "0.2.0", "description": "Local and remote push notifications for Lynx: an Android and iOS native module, and a promise-shaped facade that reaches it from the main thread.", "type": "module", "license": "MIT", diff --git a/packages/mini-helpers/CHANGELOG.md b/packages/mini-helpers/CHANGELOG.md index 5a12248..ce404a3 100644 --- a/packages/mini-helpers/CHANGELOG.md +++ b/packages/mini-helpers/CHANGELOG.md @@ -1,5 +1,68 @@ # @amritk/mini-helpers +## 0.2.0 + +### Minor Changes + +- ab6476a: Type route params from the pattern, and give `@amritk/mini-lynx` a browser + history again. + + **The pattern is now read at the type level.** `PathParams<'/users/:id'>` is + `{ id: string }`, and `matchRoute` is generic over its pattern, so + `matchRoute('/users/:id', path)` hands back `{ id: string } | null` instead of a + record that answers `string` for every key including the misspelt ones. It lives + in `@amritk/mini-helpers` beside the matcher it mirrors, because the grammar has + to be one definition or the value and type worlds drift; `path-params.test.ts` + pins the two together, down to `/v:major` being a literal segment on both sides. + + `PathParams` is `RouteParams`, so this is additive: a table annotated + `Route[]`, or built at runtime, compiles exactly as before and gets exactly what + it got before. + + **`buildPath` is the inverse.** `buildPath('/users/:id', { id })` cannot spell + the pattern wrong, forget a param or be left behind when a route is renamed, and + its values are encoded to round-trip back through `matchRoute`. A `*` wildcard + is encoded per segment, because `rest` is a path and its slashes are structure. + `navigate` still takes a plain string, deliberately — a router navigates to + concrete paths, and one that matches nothing is what a fallback screen is for — + so the check sits where the string is assembled instead. + + **`@amritk/mini-lynx/router` gains `route()` and `AnyRoute`.** + `route('/users/:id', (params) => …, meta)` keeps the pattern's literal type + alive so the view is handed a `() => { id: string }`. Its three arguments are + not a style choice: folded into one object literal, the pattern and the metadata + compete for inference against an intersection, and the metadata loses — a tab + bar reading `route.label` would get `unknown` back. + + `Route` is now `Route

`, and the router's generics are + constrained by the new `AnyRoute`. `Route<'/users/:id'>` is deliberately not + assignable to `Route` — its `view` demands the narrow getter, and the + widened form can only promise the flat record — so a table of mixed patterns + needs a constraint that admits all of them. `render-route.ts` owns the single + cast that reconciles the two. Existing tables keep working: `Route` with no type + argument is what it always was. + + **`createBrowserHistory` is back, on `@amritk/mini-lynx/router/browser`.** It is + a `RouterHistory` and nothing else, because the rest of routing is already + target-free — a device build swaps `createMemoryHistory()` back in and changes + nothing else. It takes a `base` prefix, and it stamps its depth into + `history.state` rather than reading `window.history.length`, which counts the + whole tab and is wrong in both directions once the user has gone back. That + stamp is what survives the two things a memory stack never faces: a reload + mid-stack and a forward button. + + It is the only module in the package that names `window`, and it is quarantined + accordingly. `src/router/browser/` is excluded from the main compiler pass and + covered by `tsconfig.dom.json`, so the platform-free rule the rest of the + package keeps stays something the compiler enforces rather than a convention. + Hash mode is not included: configuring a host for an SPA fallback is one line, + where a second URL grammar would be one the device build can never use. + + **`stripBase` moved into `@amritk/mini-helpers`,** since both routers now read a + browser pathname and a base that meant something slightly different on each side + is exactly the drift that package exists to prevent. `@amritk/mini`'s router + surface is unchanged apart from re-exporting `buildPath` and `PathParams`. + ## 0.1.0 ### Minor Changes diff --git a/packages/mini-helpers/package.json b/packages/mini-helpers/package.json index 45a4686..bd22f08 100644 --- a/packages/mini-helpers/package.json +++ b/packages/mini-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@amritk/mini-helpers", - "version": "0.1.0", + "version": "0.2.0", "description": "The pure helpers @amritk/mini and @amritk/mini-lynx both need: no reactivity, no platform, no dependencies.", "type": "module", "license": "MIT", diff --git a/packages/mini-lynx-native/CHANGELOG.md b/packages/mini-lynx-native/CHANGELOG.md new file mode 100644 index 0000000..7756b26 --- /dev/null +++ b/packages/mini-lynx-native/CHANGELOG.md @@ -0,0 +1,71 @@ +# @amritk/mini-lynx-native + +## 0.2.0 + +### Minor Changes + +- e025ac7: Add native modules for Lynx, starting with push notifications. + + Lynx ships no notifications module, and Sparkling's `sparkling-notifications` is + a reserved npm name with no implementation behind it — so the official answer is + still "write native code and send it into your Lynx code". These two packages + are that, plus the piece it needs first. + + **`@amritk/mini-lynx-native`** is the wire. Lynx's `NativeModules` and + `GlobalEventEmitter` are background-thread globals, while `@amritk/mini-lynx` + renders on the main thread because the Element PAPI is a main-thread API — so a + component reaching for a native module finds `undefined`, with nothing to read. + The package carries calls one way and events the other: `callNative` and + `callNativeAsync` for the two shapes a Lynx native method comes in, + `isNativeModuleAvailable` for feature detection, and `onNativeEvent` for + `sendGlobalEvent`. Calls made before the background half is installed are queued + rather than lost, because the main-thread chunk usually runs first. The + `/background` subpath is the half an app installs in its background chunk, in + one line; `/testing` ships the fakes both halves run against. + + **`@amritk/lynx-notifications`** is the first module built on it: local and + remote push, with an Android implementation (`NotificationManager`, + `AlarmManager`, FCM) and an iOS one (`UNUserNotificationCenter`, APNs) declared + to Lynx's autolinker through `lynx.lib.json`. The JavaScript surface is promises + and subscriptions rather than signals, deliberately — a second edge onto the + signal engine is how a consumer ends up with two reactive graphs that cannot see + each other's writes. + + Both native halves are compiled in CI — Gradle against the real + `org.lynxsdk.lynx:lynx` AAR for Android, `pod lib lint` against the real Lynx pod + on a macOS runner for iOS — and `src/native-contract.test.ts` pins their method + names, arities and event strings against the TypeScript, which is the one class + of drift no compiler on either side can catch. `bun run check:android` runs the + Android compile locally and skips with an explanation when there is no SDK. + + **None of that has run on a device.** Permission flows, `AlarmManager` under + Doze, APNs registration and FCM delivery are unverified. The caveat is carried in + the package's `README.md`, `AI.md` and `AGENTS.md`. + +### Patch Changes + +- 5101aa7: Correct the two mis-call failure modes in `AI.md`, and pin them with tests. + + The gotchas list had them the wrong way round: it said `callNative` on a + callback method never settles and `callNativeAsync` on a returning one gives + you `undefined`. The bridge does the opposite, and it is not a close call — + the `return` form always replies, so `callNative` always settles, while the + `callback` form appends a callback that a returning method ignores, so nothing + ever replies at all. + + ```ts + // settles: with `undefined` for a method that stores the callback, or as a + // rejection when the method reaches for the argument it was not handed + await callNative("StorageModule", "loadValue", "profile"); + + // never settles: the appended callback is an argument the method ignores + await callNativeAsync("StorageModule", "getValue", "token"); + ``` + + Both are now cases in `channel.test.ts`, because the symmetric-sounding summary + is the half that sends people looking in the wrong place — a promise that never + settles reads as a thread problem, and it is a one-word choice at the call site. + + Found by wiring the package into `apps/playground-mini-lynx`, which now has a + screen per native package driving them through each package's own published + fake. diff --git a/packages/mini-lynx-native/package.json b/packages/mini-lynx-native/package.json index 745f6f6..04db53a 100644 --- a/packages/mini-lynx-native/package.json +++ b/packages/mini-lynx-native/package.json @@ -1,6 +1,6 @@ { "name": "@amritk/mini-lynx-native", - "version": "0.1.0", + "version": "0.2.0", "description": "The thread hop between a main-thread mini-lynx tree and Lynx's background-thread NativeModules, as a typed request/reply channel.", "type": "module", "license": "MIT", diff --git a/packages/mini-lynx/CHANGELOG.md b/packages/mini-lynx/CHANGELOG.md index 9d2e435..77eaa39 100644 --- a/packages/mini-lynx/CHANGELOG.md +++ b/packages/mini-lynx/CHANGELOG.md @@ -1,5 +1,205 @@ # @amritk/mini-lynx +## 0.4.0 + +### Minor Changes + +- 59983c4: Add `@amritk/mini-lynx/keyboard` — the soft keyboard as a signal, and the + layouts that move out of its way. + + Lynx does not avoid the keyboard for you. `` does not do it, the docs say + so outright, and what the engine offers is a single global event — + `keyboardstatuschanged`, carrying `'on' | 'off'` and a height. Every Lynx app + with a form writes the layout on top of that event, and the three mistakes are + always the same: lifting by the whole keyboard rather than by the overlap, + adding the bottom safe-area inset on top of a keyboard that already covers it, + and clearing on `blur` — which makes the screen flinch when focus moves between + two adjacent fields. + + The wiring is two lines and no provider: + + ```tsx + trackKeyboard() // once, next to setEngine + + + + + + ``` + + `ref` is this runtime's element-extension seam, so a control reports its own + focus and the container reads it — nothing is threaded between them. + + - **`trackKeyboard(options?)`** subscribes to the engine's `GlobalEventEmitter` + and feeds `keyboardHeight()`. The emitter is an option because Lynx's own + compatibility data lists `keyboardstatuschanged` as **unsupported on the web**: + a DOM build reports the keyboard from `visualViewport` and passes it in, and + everything downstream is the same code. + - **`keyboardLift({ inset, offset })`** is `max(0, height - inset) + offset` + while open and exactly zero while closed — the offset included, because a gap + above a keyboard that is not there is a hole in the layout. + - **`keepAboveKeyboard()`** measures the focused field against a bounds element + and answers how far a container has to rise, feeding the rise already applied + back into the next measurement so moving between fields cannot drop the + container back to rest. Both rects come from one coordinate space, so nothing + here needs pixel ratios or a status-bar height — which is the part `lynx-ui` + pays for with an `androidStatusBarPlusBottomBarHeight` prop. + - **``** is that wired to a style binding, with + `behavior='translate'` (rise by the measured overlap) or `'padding'` (reserve + the keyboard's height for a scroller). It writes only the declaration it owns + and imposes no layout of its own, and it honours `reducedMotion()` the way + `RouteStack` does. + + `/keyboard` is opt-in and its own module graph, reaching sideways into exactly + one file — `elements/invoke.ts`, for the rect — which `import-boundary.test.ts` + now pins as an exact list. + + The playground gains a `/keyboard` screen, a `visualViewport` emitter, and + `__InvokeUIMethod` on its DOM Element PAPI (`boundingClientRect` and + `scrollIntoView` answered honestly, everything else reported as unimplemented + rather than invented). + + **Not verified on a device.** The arithmetic and the components are covered + against the fake engine, but the engine event itself, the units it reports, and + how a real IME animation interacts with the transition are unconfirmed on + hardware. + +- ab6476a: Type route params from the pattern, and give `@amritk/mini-lynx` a browser + history again. + + **The pattern is now read at the type level.** `PathParams<'/users/:id'>` is + `{ id: string }`, and `matchRoute` is generic over its pattern, so + `matchRoute('/users/:id', path)` hands back `{ id: string } | null` instead of a + record that answers `string` for every key including the misspelt ones. It lives + in `@amritk/mini-helpers` beside the matcher it mirrors, because the grammar has + to be one definition or the value and type worlds drift; `path-params.test.ts` + pins the two together, down to `/v:major` being a literal segment on both sides. + + `PathParams` is `RouteParams`, so this is additive: a table annotated + `Route[]`, or built at runtime, compiles exactly as before and gets exactly what + it got before. + + **`buildPath` is the inverse.** `buildPath('/users/:id', { id })` cannot spell + the pattern wrong, forget a param or be left behind when a route is renamed, and + its values are encoded to round-trip back through `matchRoute`. A `*` wildcard + is encoded per segment, because `rest` is a path and its slashes are structure. + `navigate` still takes a plain string, deliberately — a router navigates to + concrete paths, and one that matches nothing is what a fallback screen is for — + so the check sits where the string is assembled instead. + + **`@amritk/mini-lynx/router` gains `route()` and `AnyRoute`.** + `route('/users/:id', (params) => …, meta)` keeps the pattern's literal type + alive so the view is handed a `() => { id: string }`. Its three arguments are + not a style choice: folded into one object literal, the pattern and the metadata + compete for inference against an intersection, and the metadata loses — a tab + bar reading `route.label` would get `unknown` back. + + `Route` is now `Route

`, and the router's generics are + constrained by the new `AnyRoute`. `Route<'/users/:id'>` is deliberately not + assignable to `Route` — its `view` demands the narrow getter, and the + widened form can only promise the flat record — so a table of mixed patterns + needs a constraint that admits all of them. `render-route.ts` owns the single + cast that reconciles the two. Existing tables keep working: `Route` with no type + argument is what it always was. + + **`createBrowserHistory` is back, on `@amritk/mini-lynx/router/browser`.** It is + a `RouterHistory` and nothing else, because the rest of routing is already + target-free — a device build swaps `createMemoryHistory()` back in and changes + nothing else. It takes a `base` prefix, and it stamps its depth into + `history.state` rather than reading `window.history.length`, which counts the + whole tab and is wrong in both directions once the user has gone back. That + stamp is what survives the two things a memory stack never faces: a reload + mid-stack and a forward button. + + It is the only module in the package that names `window`, and it is quarantined + accordingly. `src/router/browser/` is excluded from the main compiler pass and + covered by `tsconfig.dom.json`, so the platform-free rule the rest of the + package keeps stays something the compiler enforces rather than a convention. + Hash mode is not included: configuring a host for an SPA fallback is one line, + where a second URL grammar would be one the device build can never use. + + **`stripBase` moved into `@amritk/mini-helpers`,** since both routers now read a + browser pathname and a base that meant something slightly different on each side + is exactly the drift that package exists to prevent. `@amritk/mini`'s router + surface is unchanged apart from re-exporting `buildPath` and `PathParams`. + +### Patch Changes + +- 1b6c33d: Bring every package's shipped `AI.md` back in line with what that package + actually publishes, and add `bun run check:ai-docs` so it cannot drift again. + + The files had gone stale in the way generated-and-committed docs always do — + silently, and only for the audience that cannot file an issue about it. + `@amritk/mini` never documented `watch`, `template`, the typed `matchRoute` / + `buildPath` re-exports on `/router`, `Field` on `/forms`, or the `/vite` subpath + at all; `@amritk/mini-lynx` was missing `computed` / `effectScope`, + `fadeTransition`, `keepAboveKeyboard` and `HANDLER_PREFIX`; + `@amritk/lynx-notifications` documented neither its `/testing` subpath nor the + fake behind it. All four native packages exported `MODULE` and `EVENTS` with no + mention of what they are for, and only `@amritk/lynx-dialogs` showed how to wire + a fake into `installNativeBridge` — which is the one thing a consumer testing + its own screens needs. + + Two accuracy fixes matter more than the additions. Every native package's + _Status_ section claimed the Objective-C compiles against the real Lynx pod; the + macOS CI job was disabled on cost, so it now compiles only when somebody runs + `pod lib lint` by hand, and the docs say that. And `@amritk/lynx-dialogs` never + carried a _Status_ section at all, so nothing in it told a reader that none of + it has run on a device. + + `bun run check:ai-docs` reads each package's `exports` and fails on a runtime + export, a published subpath, or (for a package shipping native sources) a + _Status_ section its `AI.md` never mentions. It runs early in CI, before the + build. Exports no consumer ever writes — the tree operations the JSX transform + calls, and the like — are listed in `INTERNAL_EXPORTS` with the reason. + +- d11610b: Cut main-thread work out of the paths every screen runs. + + This runtime is main-thread because the Element PAPI is, so the work it does + building and updating a tree is not work a background thread can absorb — it is + work the frame has to fit around. Five changes, all in the hot paths, none of + them changing an API: + + - `jsx-runtime.ts` walks props with `for…in` instead of `Object.entries`, which + was allocating an array plus a pair per prop, per element. It was the single + hottest function in a CPU profile of building a thousand-row list. + - `apply-prop.ts` remembers the event-prefix parse per prop name. Every prop + used to pay up to six `startsWith` scans on its way to `__SetAttribute`; a + given spelling is now scanned once for the life of the app. + - `style/to-css-name.ts` and `style/to-style-text.ts` do the same for the CSS + spelling of a style key and the unitless verdict on it. Both ran a regex per + key per write, which for a reactive style bag — `style={() => ({ paddingBottom: +keyboardHeight() })}` — meant rediscovering the same answers every frame. + - `add-event.ts` no longer copies a handler set of one on every delivered event, + and builds that set empty rather than from an iterable it had to allocate. The + dispatcher runs on every frame of a scroll, where its garbage competes with + the layout it is scrolling. + - `resolve-class.ts` collects into one accumulator instead of + `map`/`filter`/`join`, which allocated three arrays per array level and four + per toggle map to produce one string. A reactive `class` is the binding an app + re-runs most. + + Measured against an engine that does nothing but keep the tree — so the number + is the runtime's own cost and not a host's — building a thousand rows went from + ~9.5ms to ~7ms, about a quarter. + + Separately, `testing/create-fake-engine.ts` trims its call log in batches + instead of on every call. It was doing `calls.slice(-1000)` per PAPI call once + past the first thousand, so a suite that builds a thousand rows paid twenty-five + million array writes for a log it keeps a thousand lines of — which also meant + the package's own reconciler benchmark was mostly measuring the fake. `calls()` + slices on the way out, so the window a caller sees is unchanged. End to end that + takes `create 10,000 rows` in `bun run bench:reconciler` from ~840ms to ~170ms, + and `create 1,000 rows` from ~105ms to ~20ms, with the engine-call counts + identical. + + The core's gzipped byte budget moves 5449 → 5559 to pay for the lookup tables. + See the note in `core-size-budget.test.ts` for the reasoning; it is the third + deliberate move. + +- Updated dependencies [ab6476a] + - @amritk/mini-helpers@0.2.0 + ## 0.3.0 ### Minor Changes diff --git a/packages/mini-lynx/package.json b/packages/mini-lynx/package.json index 4d1cc68..b9dd960 100644 --- a/packages/mini-lynx/package.json +++ b/packages/mini-lynx/package.json @@ -1,6 +1,6 @@ { "name": "@amritk/mini-lynx", - "version": "0.3.0", + "version": "0.4.0", "description": "A signals runtime for Lynx: real elements created once and mutated forever, driving Lynx's Element PAPI directly, with no virtual tree.", "type": "module", "license": "MIT", diff --git a/packages/mini/CHANGELOG.md b/packages/mini/CHANGELOG.md index 2f34ea7..b9ec944 100644 --- a/packages/mini/CHANGELOG.md +++ b/packages/mini/CHANGELOG.md @@ -1,5 +1,101 @@ # @amritk/mini +## 0.7.0 + +### Minor Changes + +- ab6476a: Type route params from the pattern, and give `@amritk/mini-lynx` a browser + history again. + + **The pattern is now read at the type level.** `PathParams<'/users/:id'>` is + `{ id: string }`, and `matchRoute` is generic over its pattern, so + `matchRoute('/users/:id', path)` hands back `{ id: string } | null` instead of a + record that answers `string` for every key including the misspelt ones. It lives + in `@amritk/mini-helpers` beside the matcher it mirrors, because the grammar has + to be one definition or the value and type worlds drift; `path-params.test.ts` + pins the two together, down to `/v:major` being a literal segment on both sides. + + `PathParams` is `RouteParams`, so this is additive: a table annotated + `Route[]`, or built at runtime, compiles exactly as before and gets exactly what + it got before. + + **`buildPath` is the inverse.** `buildPath('/users/:id', { id })` cannot spell + the pattern wrong, forget a param or be left behind when a route is renamed, and + its values are encoded to round-trip back through `matchRoute`. A `*` wildcard + is encoded per segment, because `rest` is a path and its slashes are structure. + `navigate` still takes a plain string, deliberately — a router navigates to + concrete paths, and one that matches nothing is what a fallback screen is for — + so the check sits where the string is assembled instead. + + **`@amritk/mini-lynx/router` gains `route()` and `AnyRoute`.** + `route('/users/:id', (params) => …, meta)` keeps the pattern's literal type + alive so the view is handed a `() => { id: string }`. Its three arguments are + not a style choice: folded into one object literal, the pattern and the metadata + compete for inference against an intersection, and the metadata loses — a tab + bar reading `route.label` would get `unknown` back. + + `Route` is now `Route

`, and the router's generics are + constrained by the new `AnyRoute`. `Route<'/users/:id'>` is deliberately not + assignable to `Route` — its `view` demands the narrow getter, and the + widened form can only promise the flat record — so a table of mixed patterns + needs a constraint that admits all of them. `render-route.ts` owns the single + cast that reconciles the two. Existing tables keep working: `Route` with no type + argument is what it always was. + + **`createBrowserHistory` is back, on `@amritk/mini-lynx/router/browser`.** It is + a `RouterHistory` and nothing else, because the rest of routing is already + target-free — a device build swaps `createMemoryHistory()` back in and changes + nothing else. It takes a `base` prefix, and it stamps its depth into + `history.state` rather than reading `window.history.length`, which counts the + whole tab and is wrong in both directions once the user has gone back. That + stamp is what survives the two things a memory stack never faces: a reload + mid-stack and a forward button. + + It is the only module in the package that names `window`, and it is quarantined + accordingly. `src/router/browser/` is excluded from the main compiler pass and + covered by `tsconfig.dom.json`, so the platform-free rule the rest of the + package keeps stays something the compiler enforces rather than a convention. + Hash mode is not included: configuring a host for an SPA fallback is one line, + where a second URL grammar would be one the device build can never use. + + **`stripBase` moved into `@amritk/mini-helpers`,** since both routers now read a + browser pathname and a base that meant something slightly different on each side + is exactly the drift that package exists to prevent. `@amritk/mini`'s router + surface is unchanged apart from re-exporting `buildPath` and `PathParams`. + +### Patch Changes + +- 1b6c33d: Bring every package's shipped `AI.md` back in line with what that package + actually publishes, and add `bun run check:ai-docs` so it cannot drift again. + + The files had gone stale in the way generated-and-committed docs always do — + silently, and only for the audience that cannot file an issue about it. + `@amritk/mini` never documented `watch`, `template`, the typed `matchRoute` / + `buildPath` re-exports on `/router`, `Field` on `/forms`, or the `/vite` subpath + at all; `@amritk/mini-lynx` was missing `computed` / `effectScope`, + `fadeTransition`, `keepAboveKeyboard` and `HANDLER_PREFIX`; + `@amritk/lynx-notifications` documented neither its `/testing` subpath nor the + fake behind it. All four native packages exported `MODULE` and `EVENTS` with no + mention of what they are for, and only `@amritk/lynx-dialogs` showed how to wire + a fake into `installNativeBridge` — which is the one thing a consumer testing + its own screens needs. + + Two accuracy fixes matter more than the additions. Every native package's + _Status_ section claimed the Objective-C compiles against the real Lynx pod; the + macOS CI job was disabled on cost, so it now compiles only when somebody runs + `pod lib lint` by hand, and the docs say that. And `@amritk/lynx-dialogs` never + carried a _Status_ section at all, so nothing in it told a reader that none of + it has run on a device. + + `bun run check:ai-docs` reads each package's `exports` and fails on a runtime + export, a published subpath, or (for a package shipping native sources) a + _Status_ section its `AI.md` never mentions. It runs early in CI, before the + build. Exports no consumer ever writes — the tree operations the JSX transform + calls, and the like — are listed in `INTERNAL_EXPORTS` with the reason. + +- Updated dependencies [ab6476a] + - @amritk/mini-helpers@0.2.0 + ## 0.6.0 ### Minor Changes diff --git a/packages/mini/package.json b/packages/mini/package.json index 3b7ae6f..1abf7c9 100644 --- a/packages/mini/package.json +++ b/packages/mini/package.json @@ -1,6 +1,6 @@ { "name": "@amritk/mini", - "version": "0.6.0", + "version": "0.7.0", "description": "A deliberately tiny signals-based UI layer: reactive DOM bindings plus a compilerless JSX runtime.", "type": "module", "license": "MIT",