@junoput01/junoui 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +209 -0
- package/README.md +17 -16
- package/dist/android/dimens.xml +2 -0
- package/dist/classes.json +1774 -0
- package/dist/css/juno-tokens.css +10 -0
- package/dist/css/juno.css +1057 -56
- package/dist/flutter/juno_tokens.dart +10 -0
- package/dist/ios/JunoTokens.swift +10 -0
- package/dist/js/tokens.js +16 -0
- package/dist/json/tokens.json +51 -0
- package/dist/rust/juno_tokens.rs +383 -0
- package/dist/scss/_juno-tokens.scss +10 -0
- package/docs/accessibility.md +6 -0
- package/docs/browser-support.md +3 -0
- package/docs/components/button.md +11 -2
- package/docs/components/canvas-ink.md +71 -0
- package/docs/components/dock.md +34 -0
- package/docs/components/fold-slot.md +26 -1
- package/docs/components/gizmo.md +114 -0
- package/docs/components/swatch.md +95 -0
- package/docs/components/tree.md +112 -0
- package/docs/conformance-kit.md +255 -0
- package/docs/getting-started.md +14 -0
- package/docs/integration.md +52 -6
- package/docs/ios-conformance.md +200 -3
- package/docs/ios-pwa.md +273 -0
- package/docs/native.md +38 -1
- package/docs/tokens-reference.md +15 -0
- package/package.json +8 -2
- package/src/css/base.css +35 -43
- package/src/css/components/button.css +42 -2
- package/src/css/components/canvas-ink.css +97 -0
- package/src/css/components/dock.css +65 -6
- package/src/css/components/fold-slot.css +49 -3
- package/src/css/components/gizmo.css +238 -0
- package/src/css/components/segmented.css +15 -2
- package/src/css/components/swatch.css +187 -0
- package/src/css/components/tree.css +259 -0
- package/src/css/touch-surfaces.mjs +95 -0
- package/tools/gizmo.mjs +144 -0
- package/tools/testing.mjs +177 -0
- package/tools/tree.mjs +178 -0
package/docs/getting-started.md
CHANGED
|
@@ -48,6 +48,16 @@ Two things depend on it, and both fail **silently**:
|
|
|
48
48
|
|
|
49
49
|
Details and sources: [ios-conformance.md](./ios-conformance.md).
|
|
50
50
|
|
|
51
|
+
### If the page can end up on a Home Screen
|
|
52
|
+
|
|
53
|
+
From iOS/iPadOS 26 that is every page — "there are now zero requirements for
|
|
54
|
+
'installability'", and the user decides with an **Open as Web App** toggle when
|
|
55
|
+
they add it. So junoui's `display-mode: standalone` rules can run on a site that
|
|
56
|
+
never opted in. What to declare (or knowingly not declare), which status-bar
|
|
57
|
+
style does what, and the one stylesheet copy that must be inline in the `<head>`
|
|
58
|
+
rather than in your bundle:
|
|
59
|
+
[the consumer `<head>` contract](./ios-conformance.md#becoming-a-home-screen-web-app-the-consumer-head-contract).
|
|
60
|
+
|
|
51
61
|
## Browser support
|
|
52
62
|
|
|
53
63
|
| | Safari / iOS | Chrome / Edge | Firefox |
|
|
@@ -70,6 +80,10 @@ autoprefixer of its own; the field is there for your build tooling.
|
|
|
70
80
|
understand without reporting anything. The full audit, per feature, with the
|
|
71
81
|
degrade-vs-break verdict: [browser-support.md](./browser-support.md).
|
|
72
82
|
|
|
83
|
+
Shipping to a phone or a Home Screen? [ios-pwa.md](./ios-pwa.md) is the
|
|
84
|
+
five-minute version — what junoui gives you, the eight things you must supply,
|
|
85
|
+
and what it does not do at all.
|
|
86
|
+
|
|
73
87
|
## The model
|
|
74
88
|
|
|
75
89
|
- **Palette** — `standard` · `colorblind` · `soft`
|
package/docs/integration.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Integrating junoui into an app
|
|
2
2
|
|
|
3
|
-
junoui is **presentational**: tokens + a CSS component layer,
|
|
3
|
+
junoui is **presentational**: tokens + a CSS component layer, no behavioural JS. It dresses a UI
|
|
4
4
|
your app builds. This is the recipe for consuming it cleanly — and the rules for
|
|
5
5
|
extending it without breaking the "one design across all apps" goal.
|
|
6
6
|
|
|
@@ -87,9 +87,55 @@ Do not remap junoui's roles to a brand hue — that erases the shared meaning ac
|
|
|
87
87
|
Rule of thumb: extend **additively and namespaced**. If every app injects its own palette
|
|
88
88
|
into junoui, the single-design guarantee dies.
|
|
89
89
|
|
|
90
|
-
## 7.
|
|
90
|
+
## 7. Guarding your class names
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
A `juno-*` class name in your source is a string that has to match something in
|
|
93
|
+
junoui's stylesheet, and nothing checks it. When it does not match, nothing
|
|
94
|
+
fails: the file compiles, the tests pass, and the element renders as unstyled UA
|
|
95
|
+
defaults. One consumer shipped eleven such names in a dialog; on a phone the
|
|
96
|
+
result was a confirm button off the bottom of the screen with no way to reach it.
|
|
97
|
+
|
|
98
|
+
junoui ships the check:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
import { assertJunoClasses } from 'junoui/testing';
|
|
102
|
+
|
|
103
|
+
it('every juno class this app names exists', () => {
|
|
104
|
+
assertJunoClasses(['src/**/*.tsx'], {
|
|
105
|
+
// names YOUR stylesheet defines in the juno- namespace. Each one is a
|
|
106
|
+
// claim you are making — check it against your own sheet.
|
|
107
|
+
allowed: ['juno-icons-subset'],
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
It reads `junoui/classes.json`, the manifest this build generates from its own
|
|
113
|
+
selectors, so it cannot drift from what you installed. It throws — no framework
|
|
114
|
+
needed — and it throws rather than passing when the globs match no files.
|
|
115
|
+
|
|
116
|
+
**What it answers:** "junoui ships nothing by this name." **What it does not:**
|
|
117
|
+
whether the class still does what your component assumes. A class that exists
|
|
118
|
+
but was repurposed upstream passes.
|
|
119
|
+
|
|
120
|
+
The manifest is readable directly (`junoui/classes.json`) for anything else you
|
|
121
|
+
want to assert: `all`, `public` (the documented subset), `roles`, `components`
|
|
122
|
+
grouped BEM-wise, plus the other namespaces junoui ships and a consumer writes
|
|
123
|
+
as bare strings — `tokens`, `keyframes`, `icons`.
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
import manifest from 'junoui/classes.json' with { type: 'json' };
|
|
127
|
+
manifest.components.seg; // { block, elements: ['juno-seg__opt'], modifiers: [...] }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 8. Stateful behavior stays in your app
|
|
131
|
+
|
|
132
|
+
junoui ships no behavioural JS. Focus traps, scroll locking, gesture handling, list
|
|
133
|
+
virtualization, runtime popover positioning, data — all yours (or a future sibling
|
|
134
|
+
`junoui-<framework>` package). junoui gives you the stable, semantic DOM (real elements,
|
|
135
|
+
BEM classes, ARIA hooks) to wire behavior + analytics onto; see
|
|
136
|
+
[accessibility.md](./accessibility.md) for the per-component ARIA contract.
|
|
137
|
+
|
|
138
|
+
The only JS in the package is the two icon-sprite helpers, `icons/inline` and
|
|
139
|
+
`icons/install` — see [icon-subsetting.md](./icon-subsetting.md). The full
|
|
140
|
+
does-not-do list for phones and Home-Screen web apps is in
|
|
141
|
+
[ios-pwa.md](./ios-pwa.md).
|
package/docs/ios-conformance.md
CHANGED
|
@@ -9,7 +9,10 @@ value into a wrong one later.
|
|
|
9
9
|
|
|
10
10
|
This page is about **metrics and behaviour** on iOS. For _which iOS versions
|
|
11
11
|
junoui runs on at all_ — the supported floor, what degrades below it, and what
|
|
12
|
-
breaks — see [browser-support.md](./browser-support.md).
|
|
12
|
+
breaks — see [browser-support.md](./browser-support.md). If you are about to
|
|
13
|
+
integrate and want the bounded version first — what is free, what you supply,
|
|
14
|
+
what junoui does not do — start at [ios-pwa.md](./ios-pwa.md) and come back
|
|
15
|
+
here for the derivations.
|
|
13
16
|
|
|
14
17
|
> **Verifying anything here.** Apple's HIG is a JavaScript app: a plain `curl`
|
|
15
18
|
> returns an empty shell. Check the backing DocC JSON instead —
|
|
@@ -269,6 +272,93 @@ that has a scrollbar. Every call site above either caps well below `100vw`
|
|
|
269
272
|
scrollbar's width (`calc(100vw - var(--juno-space-24))`). Keep it that way; a
|
|
270
273
|
bare `inline-size: 100vw` is a bug.
|
|
271
274
|
|
|
275
|
+
## Becoming a Home-Screen web app: the consumer `<head>` contract
|
|
276
|
+
|
|
277
|
+
Everything in the next section is gated on `display-mode: standalone`. That mode
|
|
278
|
+
is not something junoui can enter for you and not something your CSS decides —
|
|
279
|
+
it is decided in the `<head>`, and now also by the user. So: what makes a page a
|
|
280
|
+
Home-Screen web app at all.
|
|
281
|
+
|
|
282
|
+
**Two ways in, and as of iOS 26 a third that nobody opts into.**
|
|
283
|
+
|
|
284
|
+
| Declaration | What it does | Source |
|
|
285
|
+
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
286
|
+
| Manifest `"display": "standalone"` (or `"fullscreen"`) | The standards route: "create a manifest file … and serve it along with your website" | [WebKit — Web Push for Web Apps](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/) |
|
|
287
|
+
| `<meta name="apple-mobile-web-app-capable" content="yes">` | Apple extension, iOS only: "Sets whether a web application runs in full-screen mode" | [Apple — Supported Meta Tags](https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html) |
|
|
288
|
+
| **Nothing at all, on iOS/iPadOS 26** | "By default, every website added to the Home Screen opens as a web app" | [WebKit features in Safari 26.0](https://webkit.org/blog/17333/webkit-features-in-safari-26-0/) |
|
|
289
|
+
|
|
290
|
+
Ship one of the first two if you _want_ standalone. Ship neither and, from iOS
|
|
291
|
+
26, you may get it anyway:
|
|
292
|
+
|
|
293
|
+
> "Simply put, there are now zero requirements for 'installability' in Safari."
|
|
294
|
+
> … "If the user prefers to add a bookmark for their browser, they can disable
|
|
295
|
+
> 'Open as Web App' when adding to Home Screen."
|
|
296
|
+
|
|
297
|
+
Read that second sentence carefully, because it is the part that changes how you
|
|
298
|
+
test: **standalone is now a user choice made at add-to-Home-Screen time, not a
|
|
299
|
+
property of your document.** The same build is both a tab and a web app
|
|
300
|
+
depending on a toggle you never see. Every `display-mode: standalone` rule
|
|
301
|
+
junoui ships — the letterbox unlock below is the whole list — therefore runs for
|
|
302
|
+
consumers who never asked for it, on a device where nothing in their `<head>`
|
|
303
|
+
said "web app".
|
|
304
|
+
|
|
305
|
+
`window.navigator.standalone` (read-only Boolean, Apple extension) reports the
|
|
306
|
+
answer at runtime; `matchMedia('(display-mode: standalone)')` is the standard
|
|
307
|
+
test and the one junoui's CSS uses. Check both — see
|
|
308
|
+
[the letterbox flag](#the-letterbox-flag-data-juno-letterboxed) for the exact
|
|
309
|
+
predicate.
|
|
310
|
+
|
|
311
|
+
### The status bar style, and what it is not for
|
|
312
|
+
|
|
313
|
+
`<meta name="apple-mobile-web-app-status-bar-style" content="…">` takes exactly
|
|
314
|
+
three values, and Apple states it "has no effect unless you first specify
|
|
315
|
+
full-screen mode using `apple-mobile-web-app-capable`":
|
|
316
|
+
|
|
317
|
+
| Value | Status bar | Web content |
|
|
318
|
+
| ------------------- | --------------------- | ------------------------------------------------------- |
|
|
319
|
+
| `default` | normal | displayed **below** the status bar |
|
|
320
|
+
| `black` | black background | displayed **below** the status bar |
|
|
321
|
+
| `black-translucent` | black and translucent | **entire screen**, partially obscured by the status bar |
|
|
322
|
+
|
|
323
|
+
`default` is the default. Only `black-translucent` puts your content under the
|
|
324
|
+
status bar, which is the case where `env(safe-area-inset-top)` stops being
|
|
325
|
+
decorative and starts being the thing keeping your top row readable.
|
|
326
|
+
|
|
327
|
+
**It is not a letterbox remedy.** `black` and `black-translucent` were each
|
|
328
|
+
tested with a fresh Home-Screen install on the device; both letterbox
|
|
329
|
+
identically (see the next section). Pick the value for the status bar you want
|
|
330
|
+
and nothing else.
|
|
331
|
+
|
|
332
|
+
- Source: [Apple — Supported Meta Tags](https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html)
|
|
333
|
+
(archived; carries no deprecation banner, and no replacement page for these
|
|
334
|
+
two tags was found on the current developer site).
|
|
335
|
+
|
|
336
|
+
### The `<head>` junoui actually needs
|
|
337
|
+
|
|
338
|
+
Three lines, in one place, none of which a stylesheet can supply:
|
|
339
|
+
|
|
340
|
+
```html
|
|
341
|
+
<!-- 1. required always — safe areas are inert without viewport-fit=cover,
|
|
342
|
+
and 1 CSS px = 1 pt only with width=device-width -->
|
|
343
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
344
|
+
|
|
345
|
+
<!-- 2. only if you want standalone deliberately; from iOS 26 the user can
|
|
346
|
+
grant it without you -->
|
|
347
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
348
|
+
<!-- or a manifest with "display": "standalone" -->
|
|
349
|
+
|
|
350
|
+
<!-- 3. if your shell paints before its CSS bundle: an inline copy of the
|
|
351
|
+
standalone unlock, because iOS samples the document at launch -->
|
|
352
|
+
<style>
|
|
353
|
+
/* … see "Consumer obligations" below … */
|
|
354
|
+
</style>
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Line 1 is expanded, with the failure modes, in
|
|
358
|
+
[getting-started.md](./getting-started.md). Line 3 is the first-parse obligation
|
|
359
|
+
below, and it is the one that is silent both ways: miss it and the app
|
|
360
|
+
letterboxes, ship it and nothing tells you it worked except a screenshot.
|
|
361
|
+
|
|
272
362
|
## Home-Screen standalone: the letterbox, and why `base.css` unlocks it
|
|
273
363
|
|
|
274
364
|
**The fact, and it is the most expensive thing this codebase has learned about
|
|
@@ -358,6 +448,111 @@ that still misbehaves. When that holds, the unlock is dead weight and can be
|
|
|
358
448
|
removed. Until then it is harmless where it does not apply, because the gate
|
|
359
449
|
excludes every non-iOS and non-installed context.
|
|
360
450
|
|
|
451
|
+
### The letterbox flag: `data-juno-letterboxed`
|
|
452
|
+
|
|
453
|
+
The unlock above _prevents_ the letterbox. Nothing above lets a page **react to
|
|
454
|
+
it happening anyway**, and it does happen — to a page whose first-parse copy of
|
|
455
|
+
the unlock is missing, whose `body::after` is taken, or which is not a junoui
|
|
456
|
+
page at all. So junoui fixes the name of that fact, once, here.
|
|
457
|
+
|
|
458
|
+
**The contract, in four lines:**
|
|
459
|
+
|
|
460
|
+
| | |
|
|
461
|
+
| --------------- | ---------------------------------------------------------------------------------------------------- |
|
|
462
|
+
| Attribute | `data-juno-letterboxed`, boolean (present or absent — the value is never read) |
|
|
463
|
+
| Element | the root element, `<html>` |
|
|
464
|
+
| Who sets it | **the app.** junoui ships no JS for this; see below |
|
|
465
|
+
| What it asserts | this window is in standalone display mode **and** is shorter than the screen it is on. Nothing else. |
|
|
466
|
+
|
|
467
|
+
**The predicate, so two consumers compute the same answer:**
|
|
468
|
+
|
|
469
|
+
```js
|
|
470
|
+
const SLACK_PX = 1;
|
|
471
|
+
|
|
472
|
+
const standalone =
|
|
473
|
+
matchMedia('(display-mode: standalone)').matches || navigator.standalone === true;
|
|
474
|
+
|
|
475
|
+
function isLetterboxed({ standalone, innerHeight, screenHeight }) {
|
|
476
|
+
if (!standalone) return false;
|
|
477
|
+
if (!screenHeight || !innerHeight) return false; // the browser is not telling us
|
|
478
|
+
return innerHeight < screenHeight - SLACK_PX;
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Each guard earns its place:
|
|
483
|
+
|
|
484
|
+
- **Standalone is part of the question, not an optimisation.** In a browser tab
|
|
485
|
+
the window is legitimately shorter than the screen — that is where the
|
|
486
|
+
toolbars are — and `env(safe-area-inset-*)` reads `0` there anyway, so
|
|
487
|
+
flagging a tab would be both wrong and pointless.
|
|
488
|
+
- **One pixel of slack**, because the comparison is between two integers from
|
|
489
|
+
different APIs describing the same screen; an exact test is brittle for no
|
|
490
|
+
benefit.
|
|
491
|
+
- **A zero guard**, because `screen.height === 0` means the browser is not
|
|
492
|
+
answering, and "shorter than nothing" is not a verdict.
|
|
493
|
+
|
|
494
|
+
**Watch it; do not sample it once.** The window corrects itself to the full
|
|
495
|
+
height unprompted, between **6 seconds and 43 minutes** after navigation, and
|
|
496
|
+
then holds for the life of that document. Re-evaluate on
|
|
497
|
+
`visualViewport.resize` — the event that fired for all seven observed
|
|
498
|
+
corrections — plus `window.resize` and `orientationchange`, because rotation
|
|
499
|
+
changes both dimensions and only the latter is guaranteed to report it.
|
|
500
|
+
|
|
501
|
+
**What CSS may key off it — one thing.** Inside the 812 px window
|
|
502
|
+
`env(safe-area-inset-bottom)` still reports **34**, but the home indicator is at
|
|
503
|
+
screen y 840–874, _outside_ the window. A layout that correctly honours the
|
|
504
|
+
inset is reserving room for something that is not in the view, on an edge
|
|
505
|
+
already 62 px clear of the glass. So the sanctioned use is to zero the bottom
|
|
506
|
+
inset, and only the bottom inset:
|
|
507
|
+
|
|
508
|
+
```css
|
|
509
|
+
:root {
|
|
510
|
+
--app-safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
511
|
+
}
|
|
512
|
+
html[data-juno-letterboxed] {
|
|
513
|
+
--app-safe-bottom: 0px;
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
Do **not** key the top inset off it, and do not use it to disable the unlock.
|
|
518
|
+
The 62 px top strip is genuinely reserved by the window manager; only the bottom
|
|
519
|
+
inset is a phantom.
|
|
520
|
+
|
|
521
|
+
**What junoui does with it today: nothing, and that is deliberate.** junoui's
|
|
522
|
+
own clearances and the nine other `env(safe-area-inset-bottom)` call sites read
|
|
523
|
+
the raw `env()` — eleven in all, across `base.css` (2), `dock.css` (3),
|
|
524
|
+
`pillbar.css` (3), `modal.css`, `drawer.css` and `toast.css` — so a junoui app
|
|
525
|
+
in a letterboxed window over-reserves at the bottom regardless of what the app
|
|
526
|
+
sets: an `env()` inside a component's `calc()` is not overridable from app CSS.
|
|
527
|
+
Fixing that means routing every bottom call site through a single
|
|
528
|
+
`--juno-safe-bottom` indirection, which is a CSS change across six files with a
|
|
529
|
+
visual-regression pass of its own. It is tracked as
|
|
530
|
+
ticket 20260815-039, and doing it by halves is worse than not doing it: convert
|
|
531
|
+
the clearance tokens but not `dock.css`'s margin and the bar moves while its
|
|
532
|
+
clearance does not.
|
|
533
|
+
|
|
534
|
+
**Why no junoui JS module.** junoui already ships two JS entry points
|
|
535
|
+
(`icons/inline`, `icons/install`), so a third would not be unprecedented. It is
|
|
536
|
+
still wrong here: the module is ~30 lines with no junoui-specific content — it
|
|
537
|
+
reads two DOM globals and writes an attribute — and shipping it would put a
|
|
538
|
+
workaround into the public API, with a semver commitment, a release, and a test,
|
|
539
|
+
for a defect whose entire purpose is to be deleted when WebKit fixes the window.
|
|
540
|
+
A **name** costs nothing and is free to delete. Re-open this decision if the
|
|
541
|
+
flag ever gates junoui's own CSS (20260815-039): a rule junoui ships with no
|
|
542
|
+
supported way to satisfy it would be worse than either half alone.
|
|
543
|
+
|
|
544
|
+
**It is not an upstream-fix detector.** The test in the previous section needs a
|
|
545
|
+
document that _cannot_ scroll; the unlock makes the document scroll, so a
|
|
546
|
+
correctly integrated junoui app reads `false` while the bug is very much alive.
|
|
547
|
+
The flag catches residual cases, not the platform.
|
|
548
|
+
|
|
549
|
+
**Prior art, and why the name is fixed now rather than later.** nexora sets
|
|
550
|
+
`data-nx-letterboxed` from `web/src/letterbox.ts` — that module's predicate is
|
|
551
|
+
the one reproduced above, and it predates this contract. New consumers use
|
|
552
|
+
`data-juno-letterboxed`. The cost of leaving it unnamed is that the next
|
|
553
|
+
consumer invents a third spelling for the same fact and none of the three CSS
|
|
554
|
+
rules can ever move into junoui.
|
|
555
|
+
|
|
361
556
|
- Sources: nexora `CLAUDE.md` §15, entry dated 2026-08-13; the four-mode testbed
|
|
362
557
|
`web/public/expansion-demo.html` (kept as a standing rig); 201 device readings
|
|
363
558
|
collected by `scripts/viewport_probe_collect.py`. Tracked upstream as ticket
|
|
@@ -412,8 +607,10 @@ One confirmed change raises the stakes: as of iOS/iPadOS 26, **every website
|
|
|
412
607
|
added to the Home Screen opens as a web app by default** — "there are now zero
|
|
413
608
|
requirements for 'installability'". junoui's CSS may therefore run in a
|
|
414
609
|
standalone context, where `viewport-fit` and `env()` govern home-indicator and
|
|
415
|
-
Dynamic Island clearance, for sites that never opted in.
|
|
416
|
-
|
|
610
|
+
Dynamic Island clearance, for sites that never opted in. What that means for
|
|
611
|
+
your `<head>` is
|
|
612
|
+
[the consumer contract above](#becoming-a-home-screen-web-app-the-consumer-head-contract);
|
|
613
|
+
the concrete consequence is measured in
|
|
417
614
|
[Home-Screen standalone: the letterbox](#home-screen-standalone-the-letterbox-and-why-basecss-unlocks-it) —
|
|
418
615
|
a site that never asked to be a web app now inherits both the letterbox and the
|
|
419
616
|
unlock.
|
package/docs/ios-pwa.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# junoui on iOS, and in a Home-Screen web app
|
|
2
|
+
|
|
3
|
+
The bounded claim, in one page: **what you get for free, what you must supply,
|
|
4
|
+
and what junoui explicitly does not do.** Read it before integrating; the pages
|
|
5
|
+
it links carry the detail and the derivations.
|
|
6
|
+
|
|
7
|
+
Every line here carries a number or a source. Where a claim has neither, it is
|
|
8
|
+
not on this page.
|
|
9
|
+
|
|
10
|
+
**What the numbers were measured against.** junoui **v0.5.0**; the CSS feature
|
|
11
|
+
audit is against the built `dist/css/juno.css` on **2026-08-15**; the device
|
|
12
|
+
readings are an **iPhone 16 Pro (402×874 pt), iOS 18.7 / Safari 26.6**; the
|
|
13
|
+
touch-ergonomics assertions run in Playwright at **390×844** with `hasTouch` +
|
|
14
|
+
`isMobile`. One device, one build. Nothing here is extrapolated to iPads or to
|
|
15
|
+
Android.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## The floor
|
|
20
|
+
|
|
21
|
+
| | Safari / iOS | What that line means |
|
|
22
|
+
| ----------------------------------------------------- | ------------ | --------------------------------------------------- |
|
|
23
|
+
| **Hard floor** — below this, things break | **17.0** | the Popover API. Overlays stop being overlays |
|
|
24
|
+
| **Supported floor** — put this in your support matrix | **17.5** | everything works; entry animations missing below it |
|
|
25
|
+
| **Full fidelity** | **26.0** | CSS anchor positioning |
|
|
26
|
+
|
|
27
|
+
**The interesting gap is 17.5 → 26.0, not 16.x → 17.5.** Between 17.5 and 26.0
|
|
28
|
+
menus, popovers and tooltips open, close and light-dismiss correctly — and land
|
|
29
|
+
at their **static position** instead of beside their trigger, because
|
|
30
|
+
`position-area` / `position-try-fallbacks` / `position-anchor` (**10 uses**) are
|
|
31
|
+
Safari 26. The surface works; it is in the wrong place. That is a real visual
|
|
32
|
+
defect and it is easy to mistake for a break.
|
|
33
|
+
|
|
34
|
+
Below **17.0** the Popover API is absent and junoui **hides** `.juno-menu`,
|
|
35
|
+
`.juno-popover` and the top-layer tooltip behind
|
|
36
|
+
`@supports not selector(:popover-open)`. That is not a fix. Unguarded, the UA
|
|
37
|
+
rule that hides a closed popover does not exist either, so a **256–280 px
|
|
38
|
+
invisible fixed panel** sits at its static position swallowing taps on whatever
|
|
39
|
+
it covers. junoui trades that for "the panel is absent" — absent beats
|
|
40
|
+
invisibly-present — and your app branches on
|
|
41
|
+
`CSS.supports('selector(:popover-open)')`.
|
|
42
|
+
|
|
43
|
+
Full per-feature audit, with the degrade-or-break verdict for all 15 features:
|
|
44
|
+
[browser-support.md](./browser-support.md).
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## What you get for free
|
|
49
|
+
|
|
50
|
+
### Touch ergonomics
|
|
51
|
+
|
|
52
|
+
- **44 px tap targets on coarse pointers.** `--juno-size-tap-min` is **24 px**
|
|
53
|
+
(the WCAG 2.2 **2.5.8** AA floor) and flips to `--juno-size-tap-comfortable` =
|
|
54
|
+
**44 px** under `@media (pointer: coarse)`. Everything that sizes off it
|
|
55
|
+
inherits the promotion: `.juno-btn` (`min-height`), `.juno-input` and
|
|
56
|
+
`textarea` (`min-block-size`, the latter at 3×), `.juno-menu__item`,
|
|
57
|
+
`.juno-navbar__actions > *`, and `.juno-modal__close` on both axes.
|
|
58
|
+
**One exception, stated so you can size around it:** `.juno-pagination`'s
|
|
59
|
+
items take the promotion on the **inline** axis only — their `block-size` is a
|
|
60
|
+
fixed `--juno-space-32`, so they are 44 × 32 on touch. That clears the 24 px
|
|
61
|
+
AA floor and not the 44 px comfortable one (ticket 20260815-040).
|
|
62
|
+
- **Verified numerically, not by screenshot.**
|
|
63
|
+
`test/visual/tap-targets.spec.mjs` asserts the computed `min-height` **and**
|
|
64
|
+
the rendered box under **both** Playwright projects — `44px` under
|
|
65
|
+
`chromium-coarse`, `24px` under `chromium`. Keying the expectation by project
|
|
66
|
+
is what makes it a check rather than a baseline: if the coarse project ever
|
|
67
|
+
stops emulating touch it starts producing desktop numbers and **fails**
|
|
68
|
+
instead of quietly re-recording them.
|
|
69
|
+
- **A 16 px floor on text entry**, so iOS Safari does not zoom the page onto a
|
|
70
|
+
focused field: `.juno-input { font-size: max(16px, …) }`. Two caveats, both
|
|
71
|
+
load-bearing. **No primary WebKit or Apple source states the zoom behaviour** —
|
|
72
|
+
it is empirical, and the mitigation is kept because it is harmless, not
|
|
73
|
+
because it is documented. And it **never applied on any touch device until
|
|
74
|
+
2026-08-15**: the rule sat in `base.css`'s `@media (pointer: coarse)` block,
|
|
75
|
+
where a media query adds no specificity, so `input.css`'s own `.juno-input`
|
|
76
|
+
font-size beat it on source order. The numeric check above is what found it.
|
|
77
|
+
- **The UA tap-highlight square is off** on `.juno-btn`, `.juno-dock__item`,
|
|
78
|
+
`.juno-pillbar__item`, `.juno-tabs__tab`, `.juno-list__item`,
|
|
79
|
+
`.juno-menu__item`. Community convention — **no Apple source** — so it is
|
|
80
|
+
listed as a default, not a conformance claim.
|
|
81
|
+
|
|
82
|
+
### Safe areas
|
|
83
|
+
|
|
84
|
+
`env(safe-area-inset-*)` is used **21 times across 8 files**, and the rule for
|
|
85
|
+
which arithmetic applies is not one rule but **three buckets**:
|
|
86
|
+
|
|
87
|
+
| Bucket | Arithmetic | Why |
|
|
88
|
+
| ---------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------ |
|
|
89
|
+
| Padding on a surface that reaches the edge | `max(baseline, env(…))` — the inset **replaces** | the inset's job is to clear the cutout, not to stack |
|
|
90
|
+
| A floating element positioned **off** the edge | `calc(baseline + env(…))` — **additive** | `max()` would park the pill flush against the home indicator |
|
|
91
|
+
| The clearance tokens | `calc(height + … + env(…))` — **additive** | content must clear the control's height _and_ the inset |
|
|
92
|
+
|
|
93
|
+
Get this wrong in either direction and nothing errors. The derivation, with the
|
|
94
|
+
WebKit source: [ios-conformance.md](./ios-conformance.md#max-vs-addition--the-rule).
|
|
95
|
+
|
|
96
|
+
**All of it is inert until you ship `viewport-fit=cover`** — WebKit reports
|
|
97
|
+
every inset as `0` until then, and `contain` does not opt out. See
|
|
98
|
+
[what you must supply](#what-you-must-supply).
|
|
99
|
+
|
|
100
|
+
### Floating-nav clearance, derived rather than constant
|
|
101
|
+
|
|
102
|
+
A page under a floating dock or pillbar reserves room at its foot with
|
|
103
|
+
`padding-block-end: var(--juno-dock-clearance)`. Those tokens are **computed
|
|
104
|
+
from the bar's own parts**, not hardcoded:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
--juno-dock-h = tap-comfortable + 4×space-4 + 2×border-1 = 62px at defaults
|
|
108
|
+
--juno-pillbar-h = tap-comfortable + 2×space-4 + 2×border-1 = 54px at defaults
|
|
109
|
+
|
|
110
|
+
--juno-dock-clearance = dock-h × scale + 16 + 8 + env(safe-area-inset-bottom)
|
|
111
|
+
--juno-pillbar-clearance = pillbar-h × scale + 16 + 8 + env(safe-area-inset-bottom)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
— 86 px and 78 px plus the inset, at default tokens. **Why derived matters:**
|
|
115
|
+
the old constants (`space-72 + space-20 + env()`) promised in a code comment to
|
|
116
|
+
"stay correct when the dock geometry changes" and could not. junoui explicitly
|
|
117
|
+
invites you to re-parameterise the bubble via `--juno-size-tap-comfortable`, and
|
|
118
|
+
past a **58 px** bubble the constant reserved **less** than the pill's own
|
|
119
|
+
height plus its margin — content hid under the dock, silently, on exactly the
|
|
120
|
+
consumers who took the invitation (ticket 20260815-026).
|
|
121
|
+
|
|
122
|
+
`--juno-dock-clearance-scale` (default `1`) is the knob for a bar that **shrinks
|
|
123
|
+
while scrolling**: set it to the shrunken ratio so the reservation is made at
|
|
124
|
+
the small size. Reserving against a live height means relayouting content under
|
|
125
|
+
a finger that has already stopped moving.
|
|
126
|
+
|
|
127
|
+
### The Home-Screen standalone unlock
|
|
128
|
+
|
|
129
|
+
In `display-mode: standalone`, iOS sizes the window from the document's
|
|
130
|
+
**resting** scrollability at launch and letterboxes a document that cannot
|
|
131
|
+
scroll by exactly `env(safe-area-inset-top)` — measured **874 − 812 = 62**, a
|
|
132
|
+
black strip at the bottom of the glass on every screen. `base.css` keeps the
|
|
133
|
+
document scrollable behind your fixed shell with an invisible `body::after`
|
|
134
|
+
spacer, behind a **three-condition gate, all required**:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
@media (display-mode: standalone) only installed apps letterbox
|
|
138
|
+
and (pointer: coarse) keeps macOS Dock apps out
|
|
139
|
+
@supports (-webkit-touch-callout: none) iOS/iPadOS WebKit only
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**A bundled stylesheet cannot satisfy the obligation this creates** — see
|
|
143
|
+
[what you must supply](#what-you-must-supply). The measurements, the
|
|
144
|
+
four-structure A/B, and the 201 device readings behind it:
|
|
145
|
+
[ios-conformance.md](./ios-conformance.md#home-screen-standalone-the-letterbox-and-why-basecss-unlocks-it).
|
|
146
|
+
|
|
147
|
+
### Phone-shaped component behaviour
|
|
148
|
+
|
|
149
|
+
| Behaviour | Triggered by |
|
|
150
|
+
| ----------------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
151
|
+
| Modal becomes a bottom sheet — full width, bottom-anchored, `85dvh` cap | `@media (width <= 639.98px)` |
|
|
152
|
+
| Toast stack goes full width at the bottom edge and slides up | `@media (width <= 639.98px)` |
|
|
153
|
+
| Table stacks into rows | `@container (max-width: 480px)` |
|
|
154
|
+
| Tab strip scrolls sideways instead of wrapping | `overflow: auto hidden` + `overscroll-behavior-inline: contain` |
|
|
155
|
+
|
|
156
|
+
Note the third row is a **container** query, not a viewport one: it reflows by
|
|
157
|
+
the space the table is in, not by the size of the phone. And the first is
|
|
158
|
+
deliberately _not_ — a modal is always in the top layer, so the screen **is**
|
|
159
|
+
its container.
|
|
160
|
+
|
|
161
|
+
### Silent-failure guards already paid for
|
|
162
|
+
|
|
163
|
+
Two defects of this class shipped and were found by looking at a phone, not by
|
|
164
|
+
any tool — both fixed 2026-08-05, both now permanent:
|
|
165
|
+
|
|
166
|
+
- `-webkit-backdrop-filter` ships beside **all 7** unprefixed
|
|
167
|
+
`backdrop-filter` declarations (unsupported below Safari 18.0 — every glass
|
|
168
|
+
surface rendered flat on iOS 17).
|
|
169
|
+
- A `::-webkit-scrollbar { display: none }` companion ships beside **all 3**
|
|
170
|
+
`scrollbar-width` declarations (unsupported below Safari 18.2 — the
|
|
171
|
+
scrollable tab strip kept a visible scrollbar on iOS 17 through 18.1).
|
|
172
|
+
|
|
173
|
+
> Both are 7 and 3 **declarations**, verified in `dist/css/juno.css`. The audit
|
|
174
|
+
> table in [browser-support.md](./browser-support.md) reports 14 and 4 because
|
|
175
|
+
> it counts string occurrences, and `-webkit-backdrop-filter` contains
|
|
176
|
+
> `backdrop-filter` — so each guarded pair is counted twice. Same fact, two
|
|
177
|
+
> units.
|
|
178
|
+
|
|
179
|
+
And **zero raw `vh`** — grep the bundle and there are none. Full-height surfaces
|
|
180
|
+
use `dvh` (**9 uses**) because `vh == lvh` normatively and a `100vh` box is
|
|
181
|
+
sized as if the toolbar were retracted; the single `lvh` in the bundle is the
|
|
182
|
+
standalone unlock's spacer, which is _supposed_ to overflow. The reasoning, and
|
|
183
|
+
the rule for a component that does not exist yet:
|
|
184
|
+
[ios-conformance.md](./ios-conformance.md#the-rule-for-a-component-that-does-not-exist-yet).
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## What you must supply
|
|
189
|
+
|
|
190
|
+
junoui is a stylesheet. Everything below is outside what CSS can reach.
|
|
191
|
+
|
|
192
|
+
| # | You ship | If you don't |
|
|
193
|
+
| --- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
194
|
+
| 1 | `<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">` | every safe-area guarantee becomes a no-op, silently; and 1 CSS px stops equalling 1 pt |
|
|
195
|
+
| 2 | A standalone declaration — manifest `"display": "standalone"` or `apple-mobile-web-app-capable` — **or a decision not to** | from iOS 26 the **user** grants standalone at add-to-Home-Screen time and junoui's standalone CSS runs anyway |
|
|
196
|
+
| 3 | **An inline copy of the letterbox unlock in `<head>`**, if your shell paints before its CSS bundle | iOS samples the document at launch; `juno.css` arrives after, so the app letterboxes with the unlock present |
|
|
197
|
+
| 4 | Leave `body::after` alone, or reproduce the spacer at the same gate | the unlock has no spacer and the window letterboxes |
|
|
198
|
+
| 5 | `overscroll-behavior: contain` on your real scrollers | an inner fling chains into the unlock's ghost scroller |
|
|
199
|
+
| 6 | A popover fallback below Safari 17.0, branched on `CSS.supports('selector(:popover-open)')` | overflow navigation is unreachable — junoui hid the panel rather than leave it eating taps |
|
|
200
|
+
| 7 | Pinning `inset` on the `toggle` event below Safari 26.0 | anchored surfaces open at their static position |
|
|
201
|
+
| 8 | `data-juno-letterboxed` on `<html>`, if you want to react to a letterboxed window | nothing; the flag is opt-in — but invent your own attribute name and your CSS can never move into junoui |
|
|
202
|
+
|
|
203
|
+
**#3 is the one that catches people**, because it is silent in both directions:
|
|
204
|
+
miss it and the app letterboxes with a correct stylesheet installed, ship it and
|
|
205
|
+
nothing confirms it worked except a screenshot of the device. Rows 2, 3 and 8
|
|
206
|
+
are specified in full — declarations, status-bar-style semantics, the exact
|
|
207
|
+
letterbox predicate — in
|
|
208
|
+
[the consumer `<head>` contract](./ios-conformance.md#becoming-a-home-screen-web-app-the-consumer-head-contract).
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## What junoui explicitly does not do
|
|
213
|
+
|
|
214
|
+
This half is why the page exists. None of the following is planned, partial, or
|
|
215
|
+
"coming to the roadmap" — it is out of scope by design.
|
|
216
|
+
|
|
217
|
+
**No JS behaviour.** No focus traps, no scroll locking, no gesture or swipe
|
|
218
|
+
handling, no runtime popover positioning, no list virtualization, no state. Every
|
|
219
|
+
component renders with zero JS; what is stateful is the platform's (`<dialog>`,
|
|
220
|
+
`popover`, `<details>`) or yours. junoui does ship two JS entry points —
|
|
221
|
+
`icons/inline` and `icons/install` — and they are **icon-sprite helpers, nothing
|
|
222
|
+
more**.
|
|
223
|
+
|
|
224
|
+
**No service worker, no offline story, no cache.** No precaching, no purge, no
|
|
225
|
+
versioning, no `skipWaiting` policy. [boot-shell.md](./boot-shell.md) documents
|
|
226
|
+
the five-rung boot pattern including an app-shell service worker — that is a
|
|
227
|
+
**recipe your app implements**, with a reference implementation in another
|
|
228
|
+
repository. junoui ships none of that code.
|
|
229
|
+
|
|
230
|
+
**No native chrome.** `dist/ios/JunoTokens.swift` is **token values only**. There
|
|
231
|
+
are no UIKit or SwiftUI components. `.juno-dock` and `.juno-navbar` are CSS that
|
|
232
|
+
_looks_ like a bar; they are not the system bar and do not inherit its
|
|
233
|
+
behaviours.
|
|
234
|
+
|
|
235
|
+
**Nothing about in-app browsers.** SFSafariViewController and WKWebView-based
|
|
236
|
+
in-app browsers (Instagram, Facebook) are a **separate, unfixed path** and
|
|
237
|
+
junoui has tested neither. The one datum on record: Safari 26.0's notes claim a
|
|
238
|
+
fix for `lvh`/`vh` being sized against the small viewport in
|
|
239
|
+
SFSafariViewController, but **WebKit bug 255708 (filed 2023) is still open**, so
|
|
240
|
+
that "Fixed" is stronger than the tracker supports.
|
|
241
|
+
|
|
242
|
+
**No app-shell assets.** No web app manifest, no Home-Screen icons, no splash
|
|
243
|
+
screens, no install prompt. `src/icons/` is a **UI icon sprite** — the icons
|
|
244
|
+
inside your interface, not the icon on the Home Screen.
|
|
245
|
+
|
|
246
|
+
**No guarantee below Safari / iOS 17.0**, and the losses compound rather than
|
|
247
|
+
arriving all at once: **16.2** drops `color-mix()` (**28 uses** — every role
|
|
248
|
+
tint, border and shadow built from `var(--juno-role)`, which is the contract
|
|
249
|
+
junoui exists to encode); **16.0** drops `@container`; **15.4** drops `oklch()`
|
|
250
|
+
(**190 uses** — every token value) and `dvh`/`lvh` (**10 uses** — the app shell
|
|
251
|
+
collapses to content height). There is no fallback layer for any of them, and
|
|
252
|
+
none is planned. If you are on iOS 15 you are not using junoui.
|
|
253
|
+
|
|
254
|
+
**No verified iOS 26 story.** The claim "iOS 26 changed nothing about safe
|
|
255
|
+
areas, viewport-fit, touch behaviour or focus zoom" was **refuted** during
|
|
256
|
+
verification, so junoui's iOS 26 behaviour is _unverified_, not
|
|
257
|
+
_confirmed-safe_. Open leads — `vh` reportedly pinning to `window.outerHeight`,
|
|
258
|
+
three new tab modes yielding different `innerHeight`, iPadOS 26 windowed mode
|
|
259
|
+
reportedly returning nothing for `env(safe-area-inset-*)` — need a physical
|
|
260
|
+
device, not more documentation.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Where the detail lives
|
|
265
|
+
|
|
266
|
+
| Page | What it settles |
|
|
267
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------ |
|
|
268
|
+
| [ios-conformance.md](./ios-conformance.md) | every iOS metric with its source, the folklore named, the letterbox, the `<head>` contract |
|
|
269
|
+
| [browser-support.md](./browser-support.md) | the three floors, all 15 features with degrade-or-break, the two shipped silent failures |
|
|
270
|
+
| [getting-started.md](./getting-started.md) | the required viewport meta and what depends on it |
|
|
271
|
+
| [accessibility.md](./accessibility.md) | the WCAG 2.2 criterion behind each target size, and the per-component ARIA contract |
|
|
272
|
+
| [integration.md](./integration.md) | import order, the token bridge, and where app-specific things go |
|
|
273
|
+
| [boot-shell.md](./boot-shell.md) | the boot ladder — a pattern to implement, not code junoui ships |
|
package/docs/native.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Native
|
|
1
|
+
# Native (Android / iOS / Rust)
|
|
2
2
|
|
|
3
3
|
Native platforms can't parse `oklch()`, so colors are pre-converted to sRGB hex
|
|
4
4
|
during the build. Values match the web rendering.
|
|
@@ -43,6 +43,43 @@ let body: CGFloat = JunoTokens.fontSize14
|
|
|
43
43
|
- Colors are `UIColor` constants named `<palette><Mode><Role>` (camelCase).
|
|
44
44
|
- Dimensions are `CGFloat` constants.
|
|
45
45
|
|
|
46
|
+
## Rust
|
|
47
|
+
|
|
48
|
+
Any native Rust stack — egui, iced, Slint, Bevy, Dioxus desktop, Tauri's Rust
|
|
49
|
+
side. Vendor `dist/rust/juno_tokens.rs` into your crate, or add junoui as a
|
|
50
|
+
build dependency and copy it in `build.rs`.
|
|
51
|
+
|
|
52
|
+
```rust
|
|
53
|
+
include!(concat!(env!("OUT_DIR"), "/juno_tokens.rs")); // or `mod juno_tokens;`
|
|
54
|
+
|
|
55
|
+
let bg = STANDARD_DARK.s0.to_f32_array(); // a whole theme, picked at runtime
|
|
56
|
+
let accent = STANDARD_DARK_ACTIVE; // or one role, flat
|
|
57
|
+
let gap = SPACE_16; // f32 px
|
|
58
|
+
let fade = MOTION_DURATION_BASE_MS; // f32 milliseconds
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| Emitted as | From |
|
|
62
|
+
| -------------------------------------------------- | ---------------------------------------------------- |
|
|
63
|
+
| `Rgba` (`hex()`, `to_f32_array()`, `with_alpha()`) | every color token |
|
|
64
|
+
| `Palette` + one const per palette/mode | the same colors, grouped |
|
|
65
|
+
| `f32` | `px` lengths |
|
|
66
|
+
| `f32`, suffixed `_MS` | `ms` durations |
|
|
67
|
+
| `i32` | whole numbers (z-index, font weight) |
|
|
68
|
+
| `f32` | ratios (opacity, line height) |
|
|
69
|
+
| `&str` | CSS-authored values, verbatim (shadows, font stacks) |
|
|
70
|
+
|
|
71
|
+
**`to_f32_array()` is sRGB-encoded, not linear.** If your pipeline wants linear
|
|
72
|
+
(wgpu with a non-sRGB surface format), convert at your boundary — junoui cannot
|
|
73
|
+
know which surface you created.
|
|
74
|
+
|
|
75
|
+
**Shadows and font stacks are shipped unparsed.** A Rust renderer cannot consume
|
|
76
|
+
`0 4px 14px rgb(0 0 0 / 0.35)` directly; they are here so the values live in one
|
|
77
|
+
place, not as ready-made native input.
|
|
78
|
+
|
|
79
|
+
**Do not transcribe these values into your own crate.** That copy is exactly
|
|
80
|
+
what this target exists to stop — it goes stale on the first patch release and
|
|
81
|
+
no lint catches it.
|
|
82
|
+
|
|
46
83
|
## Keeping in sync
|
|
47
84
|
|
|
48
85
|
Re-run `npm run build` in junoui and re-copy the files (or script the copy in your
|