@vespercms/admin 0.0.0-stage → 0.1.0-next.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/README.md +119 -2
- package/dist/BodyEditor-CAGNfofv.js +3047 -0
- package/dist/a11y/FieldNotes.d.ts +23 -0
- package/dist/a11y/NotesDrawer.d.ts +23 -0
- package/dist/a11y/pdf.d.ts +3 -0
- package/dist/a11y/run.d.ts +3 -0
- package/dist/a11y/useNotes.d.ts +28 -0
- package/dist/app/App.d.ts +2 -0
- package/dist/app/Screen.d.ts +88 -0
- package/dist/app/UserMenu.d.ts +7 -0
- package/dist/app/api.d.ts +75 -0
- package/dist/app/appearance.d.ts +10 -0
- package/dist/app/browser-api.d.ts +17 -0
- package/dist/app/collection-icon.d.ts +12 -0
- package/dist/app/context.d.ts +23 -0
- package/dist/app/entries-changed.d.ts +11 -0
- package/dist/app/file-url.d.ts +13 -0
- package/dist/app/format.d.ts +4 -0
- package/dist/app/idb.d.ts +7 -0
- package/dist/app/load.d.ts +16 -0
- package/dist/app/media.d.ts +5 -0
- package/dist/app/navigation.d.ts +4 -0
- package/dist/app/pane-context.d.ts +19 -0
- package/dist/app/panes.d.ts +14 -0
- package/dist/app/router.d.ts +62 -0
- package/dist/body/BodyEditor.d.ts +42 -0
- package/dist/body/TableView.d.ts +2 -0
- package/dist/body/catalog.d.ts +47 -0
- package/dist/body/checks.d.ts +15 -0
- package/dist/body/chips.d.ts +31 -0
- package/dist/body/commands.d.ts +31 -0
- package/dist/body/document.d.ts +42 -0
- package/dist/body/insert.d.ts +69 -0
- package/dist/body/nodes.d.ts +38 -0
- package/dist/body/reveal.d.ts +13 -0
- package/dist/body/scroll.d.ts +16 -0
- package/dist/body/settings.d.ts +19 -0
- package/dist/body/shortcuts.d.ts +13 -0
- package/dist/body/table.d.ts +31 -0
- package/dist/body/ui.d.ts +30 -0
- package/dist/body/views.d.ts +9 -0
- package/dist/checks-C-0xDu-I.js +57 -0
- package/dist/cloud/CloudGate.d.ts +14 -0
- package/dist/cloud/access.d.ts +14 -0
- package/dist/cloud/changes.d.ts +26 -0
- package/dist/cloud/connection.d.ts +100 -0
- package/dist/cloud/editor.d.ts +44 -0
- package/dist/cloud/session.d.ts +62 -0
- package/dist/cloud/storage.d.ts +45 -0
- package/dist/editor/Field.d.ts +24 -0
- package/dist/editor/PagePicker.d.ts +20 -0
- package/dist/editor/arrange.d.ts +13 -0
- package/dist/editor/context.d.ts +23 -0
- package/dist/editor/copy.d.ts +9 -0
- package/dist/editor/reveal.d.ts +7 -0
- package/dist/editor/target.d.ts +21 -0
- package/dist/editor/values.d.ts +18 -0
- package/dist/geo-map-CT3OGD7O.js +110 -0
- package/dist/github/GitHubGate.d.ts +15 -0
- package/dist/github/api.d.ts +20 -0
- package/dist/github/cache.d.ts +2 -0
- package/dist/github/session.d.ts +35 -0
- package/dist/index.d.ts +3 -0
- package/dist/markdown-KUkv11EB.js +211 -0
- package/dist/media/MediaPicker.d.ts +28 -0
- package/dist/media/file-icon.d.ts +3 -0
- package/dist/media/formats.d.ts +23 -0
- package/dist/media/hygiene.d.ts +37 -0
- package/dist/media/upload.d.ts +31 -0
- package/dist/mount.d.ts +3 -0
- package/dist/pdf-m5s_uXJ3.js +24 -0
- package/dist/run-y4XRkg17.js +7 -0
- package/dist/schedule/ScheduleDialog.d.ts +28 -0
- package/dist/schedule/ScheduleSummary.d.ts +12 -0
- package/dist/schedule/ScheduledView.d.ts +8 -0
- package/dist/schedule/cloud.d.ts +21 -0
- package/dist/schedule/now.d.ts +5 -0
- package/dist/schedule/times.d.ts +37 -0
- package/dist/screens/EntryEditor.d.ts +13 -0
- package/dist/screens/EntryList.d.ts +18 -0
- package/dist/screens/Home.d.ts +6 -0
- package/dist/screens/ListPane.d.ts +6 -0
- package/dist/screens/MediaBrowser.d.ts +7 -0
- package/dist/screens/NotFound.d.ts +3 -0
- package/dist/screens/PageTree.d.ts +10 -0
- package/dist/screens/Redirects.d.ts +1 -0
- package/dist/screens/Unavailable.d.ts +8 -0
- package/dist/shortcuts-B_JMAvF9.js +4630 -0
- package/dist/studio.css +3 -0
- package/dist/studio.js +6312 -0
- package/dist/types.d.ts +76 -0
- package/dist/widgets/basic.d.ts +25 -0
- package/dist/widgets/crops.d.ts +43 -0
- package/dist/widgets/focal-point.d.ts +15 -0
- package/dist/widgets/geo-map.d.ts +10 -0
- package/dist/widgets/geo.d.ts +16 -0
- package/dist/widgets/group.d.ts +41 -0
- package/dist/widgets/icon.d.ts +9 -0
- package/dist/widgets/image.d.ts +16 -0
- package/dist/widgets/markdown-lazy.d.ts +2 -0
- package/dist/widgets/markdown.d.ts +2 -0
- package/dist/widgets/reference.d.ts +12 -0
- package/dist/widgets/registry.d.ts +32 -0
- package/dist/widgets/special.d.ts +20 -0
- package/dist/widgets/union.d.ts +36 -0
- package/package.json +63 -4
- package/src/a11y/FieldNotes.tsx +56 -0
- package/src/a11y/NotesDrawer.tsx +81 -0
- package/src/a11y/pdf.ts +45 -0
- package/src/a11y/run.ts +12 -0
- package/src/a11y/useNotes.ts +139 -0
- package/src/app/App.tsx +156 -0
- package/src/app/Screen.tsx +416 -0
- package/src/app/UserMenu.tsx +95 -0
- package/src/app/api.ts +176 -0
- package/src/app/appearance.ts +54 -0
- package/src/app/browser-api.ts +173 -0
- package/src/app/collection-icon.ts +15 -0
- package/src/app/context.tsx +32 -0
- package/src/app/entries-changed.ts +48 -0
- package/src/app/file-url.tsx +44 -0
- package/src/app/format.ts +39 -0
- package/src/app/idb.ts +82 -0
- package/src/app/load.ts +40 -0
- package/src/app/media.ts +20 -0
- package/src/app/navigation.ts +4 -0
- package/src/app/pane-context.ts +25 -0
- package/src/app/panes.tsx +513 -0
- package/src/app/router.ts +158 -0
- package/src/body/BodyEditor.tsx +1139 -0
- package/src/body/TableView.tsx +700 -0
- package/src/body/catalog.ts +198 -0
- package/src/body/checks.ts +83 -0
- package/src/body/chips.ts +150 -0
- package/src/body/commands.ts +191 -0
- package/src/body/document.ts +388 -0
- package/src/body/insert.tsx +301 -0
- package/src/body/nodes.ts +287 -0
- package/src/body/reveal.ts +93 -0
- package/src/body/scroll.ts +53 -0
- package/src/body/settings.tsx +510 -0
- package/src/body/shortcuts.ts +31 -0
- package/src/body/table.ts +192 -0
- package/src/body/ui.ts +77 -0
- package/src/body/views.tsx +268 -0
- package/src/build.ts +6 -0
- package/src/cloud/CloudGate.tsx +245 -0
- package/src/cloud/access.ts +52 -0
- package/src/cloud/changes.tsx +474 -0
- package/src/cloud/connection.ts +281 -0
- package/src/cloud/editor.tsx +250 -0
- package/src/cloud/session.ts +289 -0
- package/src/cloud/storage.ts +212 -0
- package/src/editor/Field.tsx +182 -0
- package/src/editor/PagePicker.tsx +102 -0
- package/src/editor/arrange.ts +98 -0
- package/src/editor/context.tsx +31 -0
- package/src/editor/copy.ts +30 -0
- package/src/editor/reveal.ts +38 -0
- package/src/editor/target.ts +104 -0
- package/src/editor/values.ts +71 -0
- package/src/github/GitHubGate.tsx +261 -0
- package/src/github/api.ts +81 -0
- package/src/github/cache.ts +64 -0
- package/src/github/session.ts +146 -0
- package/src/index.ts +10 -0
- package/src/media/MediaPicker.tsx +302 -0
- package/src/media/file-icon.ts +17 -0
- package/src/media/formats.ts +262 -0
- package/src/media/hygiene.ts +220 -0
- package/src/media/upload.ts +47 -0
- package/src/mount.tsx +20 -0
- package/src/schedule/ScheduleDialog.tsx +300 -0
- package/src/schedule/ScheduleSummary.tsx +59 -0
- package/src/schedule/ScheduledView.tsx +294 -0
- package/src/schedule/cloud.ts +66 -0
- package/src/schedule/now.ts +18 -0
- package/src/schedule/times.ts +133 -0
- package/src/screens/EntryEditor.tsx +1803 -0
- package/src/screens/EntryList.tsx +274 -0
- package/src/screens/Home.tsx +131 -0
- package/src/screens/ListPane.tsx +459 -0
- package/src/screens/MediaBrowser.tsx +253 -0
- package/src/screens/NotFound.tsx +19 -0
- package/src/screens/PageTree.tsx +718 -0
- package/src/screens/Redirects.tsx +522 -0
- package/src/screens/Unavailable.tsx +27 -0
- package/src/studio.css +163 -0
- package/src/types.ts +82 -0
- package/src/widgets/basic.tsx +311 -0
- package/src/widgets/crops.tsx +399 -0
- package/src/widgets/focal-point.tsx +240 -0
- package/src/widgets/geo-map.tsx +160 -0
- package/src/widgets/geo.tsx +170 -0
- package/src/widgets/group.tsx +827 -0
- package/src/widgets/icon.tsx +47 -0
- package/src/widgets/image.tsx +407 -0
- package/src/widgets/markdown-lazy.tsx +22 -0
- package/src/widgets/markdown.tsx +267 -0
- package/src/widgets/reference.tsx +196 -0
- package/src/widgets/registry.ts +104 -0
- package/src/widgets/special.tsx +286 -0
- package/src/widgets/union.tsx +233 -0
package/README.md
CHANGED
|
@@ -1,3 +1,120 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @vespercms/admin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Vesper studio: the editor served at `/admin`. One bundle for every storage mode. `vespercms` injects the page that mounts it, so sites never import this package themselves.
|
|
4
|
+
|
|
5
|
+
**Status:** Phases 2 to 3 complete, and Phase 4's half in this repository: the cloud's sign-in, drafts, Changes and review (see "The cloud" below). In place: the shell, Home, the entry list (search, status filter, sortable columns), and the editor (title as heading, fields in schema order, Details, the body in a writing view that splices changed blocks into the original text (plus a source view), save with validation and conflict handling, delete, unsaved-changes guard). Every built-in widget works, including image and file uploads with hygiene, the media browser, the reference pickers, color, geo with an optional map, slug and date-time in the site's time zone. Markdoc tags and declared MDX components are blocks in the body editor, with settings, a "/" insert list, and live validation that stops a save the site couldn't build. Tree collections open as an outline of pages with moves, order, child and section pages; the Redirects screen edits redirects and imports them from CSV; and links can be picked from the site's pages. A `fields.image()` field sets its focal point and crops behind a "Focal point & crops" disclosure. Accessibility notes sit beside the fields they concern, with a count in the top bar that opens a drawer of every note. Since the studio design pass, every screen follows Vesper's design system (Pine), and on wide screens Details opens as a drawer from the top bar.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## How it's built
|
|
9
|
+
|
|
10
|
+
The studio ships as compiled JavaScript and CSS (DECISIONS.md, "Studio"):
|
|
11
|
+
|
|
12
|
+
- `dist/studio.js`: ES module. React, React DOM, React Aria Components and `@vespercms/core` stay external, so the site's Vite bundles **one** React for the studio and the site's custom widgets.
|
|
13
|
+
- `dist/studio.css`: Tailwind 4 plus the `@vespercms/ui` tokens, compiled at publish time, with Figtree 400 and 600 (latin, from `@fontsource/figtree`) inlined, so `/admin` requests no font from Google or a CDN. Sites need no Tailwind of their own.
|
|
14
|
+
- `@vespercms/ui` is private and is bundled in.
|
|
15
|
+
|
|
16
|
+
`npm run build -w @vespercms/admin` runs `vite build` (library mode, entry `src/build.ts`) and then `tsc -p tsconfig.build.json` for type declarations. The root `npm run build` includes it. In this workspace, the `@vesper/source` condition points both exports at `src/`, so dev and tests never read `dist/`.
|
|
17
|
+
|
|
18
|
+
## Mounting
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createLocalApi, mountStudio } from '@vespercms/admin';
|
|
22
|
+
import '@vespercms/admin/studio.css';
|
|
23
|
+
|
|
24
|
+
mountStudio(document.getElementById('vesper-studio')!, {
|
|
25
|
+
manifest, // from virtual:vesper/manifest
|
|
26
|
+
api: createLocalApi('/_vesper/api'), // null on a build with no back end yet
|
|
27
|
+
widgets, // custom widgets from vesper.config.ts
|
|
28
|
+
loadMdxProps, // loads declared MDX components' Zod props schemas
|
|
29
|
+
siteName: 'Playground',
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
With `api: null` and `github` set (a built site in GitHub mode), the studio signs the editor in with GitHub first, through the site's relay, and then saves to the repository: `github: { owner, repo, branch, root, relay, apiUrl?, schemas }`, plus `routes` (the site's static routes) and `loadUrlFunctions`. `vespercms` writes all of it into the page.
|
|
34
|
+
|
|
35
|
+
## GitHub mode (spec §6.8)
|
|
36
|
+
|
|
37
|
+
- `github/session.ts`: the sign-in. The studio keeps a state and a PKCE verifier in `sessionStorage`, goes to the relay's `/authorize`, and on the way back posts the code and verifier to `/token`. Only the 8-hour access token is kept, in `sessionStorage`.
|
|
38
|
+
- `github/GitHubGate.tsx`: before the studio. It finishes a sign-in, checks the editor can push, and shows "Sign in with GitHub", "Your GitHub account can't change this site" or "GitHub couldn't be reached".
|
|
39
|
+
- `github/api.ts`: `createGitHubApi`, the same core services local mode's dev API runs, over `GitHubStorage`. It validates with the site's schemas built for the browser (`_vesper/schemas.js`), or the field model's checks when those can't load. Files come back as object URLs, and GitHub's errors are put in plain words.
|
|
40
|
+
- `github/cache.ts`: file bytes by git blob SHA in IndexedDB, so a later visit reads only what changed.
|
|
41
|
+
- Signed in, the top bar has the user menu (`@login`, Sign out of GitHub). The editor's button reads Publish, and an expired sign-in keeps the open entry's unsaved changes across the sign-in.
|
|
42
|
+
|
|
43
|
+
## The cloud (spec §6.9, §7.4)
|
|
44
|
+
|
|
45
|
+
- `cloud/session.ts`: the sign-in. A top-level redirect to the app's `/authorize` with PKCE, back to `/admin/#/callback` with a one-time code, exchanged for tokens bound to a non-extractable key (DPoP). The key and tokens are kept together in IndexedDB, so a reload stays signed in; renewals run one tab at a time (a Web Lock), since a rotated refresh token used twice ends the sign-in.
|
|
46
|
+
- `cloud/CloudGate.tsx`: before the studio. "Sign in with Vesper", with "Sign in with GitHub" too when the site has GitHub mode, and "Vesper couldn't be reached" with Try again.
|
|
47
|
+
- `cloud/storage.ts`: `CloudStorage`, the site as its drafts show it, read through the cloud's git proxy, with bytes checked against their blob SHA and cached by it. Every save is a draft. It keeps the storage contract.
|
|
48
|
+
- `cloud/connection.ts`: the back end over it (`app/browser-api.ts`, shared with GitHub mode), the drafts store screens subscribe to, and what the member may do (`access`, `draftAccess`).
|
|
49
|
+
- `cloud/editor.tsx`, in the editor: a draft saves itself a moment after typing stops, after the same checks as a save; Publish (or Request review, for those who don't publish) sends it on, with the publish gate applying there; a conflict shows both versions side by side; deploy status follows a publish; unsaved changes are kept in IndexedDB until the cloud has them.
|
|
50
|
+
- `cloud/changes.tsx`: Changes, a top-bar button with its count in words, opening a drawer with the site's drafts, "Publish 3 changes" and the reviews waiting for an approver. Home says how many drafts wait.
|
|
51
|
+
- `cloud/access.ts`: what a member sees: the collections and entries they can edit (viewers see all), a folder grant's branches in a page tree, and Redirects only with the whole site.
|
|
52
|
+
- `schedule/cloud.ts`, with the drafts store's `hold`, `release` and `backToDraft`: scheduling connected (spec §6.17). On a site that holds scheduled drafts, Publish holds a draft whose time is still to come. Cancel schedule releases a held draft, or takes a published entry still to come back to a draft. The Scheduled view says where each change stands, and Changes leaves held drafts out of its Publish.
|
|
53
|
+
|
|
54
|
+
None of it shows unless the studio is connected (spec §6.9). The tests run against `scripts/cloud-fake.mjs`, never the cloud's own code.
|
|
55
|
+
|
|
56
|
+
## Routes
|
|
57
|
+
|
|
58
|
+
Hash routes, so deep links and reloads work on any static host without rewrite rules (D16):
|
|
59
|
+
|
|
60
|
+
| Route | Screen |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `#/` | Home: collections and singletons, with entry counts and last edits |
|
|
63
|
+
| `#/c/:collection` | Entry list, or the page outline for a `tree: true` collection |
|
|
64
|
+
| `#/c/:collection/new` | New entry |
|
|
65
|
+
| `#/c/:collection/new/:parent` | New page under a parent in the tree (the parent's path, one encoded segment) |
|
|
66
|
+
| `#/c/:collection/e/:id` | Editor (the id is one encoded segment, even when it contains `/`) |
|
|
67
|
+
| `#/media` | Media browser |
|
|
68
|
+
| `#/redirects` | Redirects (from the top bar's Site menu) |
|
|
69
|
+
|
|
70
|
+
## Pages, moves and redirects (spec §6.15)
|
|
71
|
+
|
|
72
|
+
- **The outline** (`screens/PageTree.tsx`) replaces the entry list for `tree: true` collections. It's the kit's `Tree`, a treegrid: one row per page with its title, its address in muted text and its status in words ("No section page", "Draft"), collapsed beyond the first level. Enter opens a page. Each row's "…" menu (reached with the right arrow, or Shift+F10) adds a child page, moves it, moves it up or down, creates a missing section page, or deletes it. Alt+↑/↓ move the focused page. Drag and drop (React Aria's, keyboard mode included) is offered on top: a drop between pages reorders, a drop onto a page opens the move dialog. Every change is announced politely.
|
|
73
|
+
- **Moving** asks the dev API for a dry run first, and confirms in words: "This moves “Nursing” and its 3 pages to the top level. Its address becomes /nursing/. 8 links on other pages will be updated. Visitors to the old addresses will be sent to the new ones." Refusals (a static route's address, a duplicate, `maxDepth`) show in the dialog.
|
|
74
|
+
- **The Redirects screen** (`screens/Redirects.tsx`) lists every redirect with its kind in words, what needs a look (from the same cleanup as the build), one "Add redirect" button, edit and delete in each row's menu, and CSV import with a preview of what's added, replaced and skipped.
|
|
75
|
+
- **The link picker** (`editor/PagePicker.tsx`): the body editor's link dialog searches pages by title and writes the page's address, and URL fields get "Choose a page…". A field whose schema takes only full URLs gets the site's address in front, or no picker when Astro's `site` isn't set.
|
|
76
|
+
|
|
77
|
+
## Widget contract
|
|
78
|
+
|
|
79
|
+
Custom widgets receive `WidgetProps` (spec §6.3.3): `{ value, onChange, field, id, describedBy, invalid, disabled }`. The editor renders the label, help and error text. A widget labels its control through `id`, forwards `describedBy` and `invalid`, is keyboard operable, and never validates.
|
|
80
|
+
|
|
81
|
+
## Editor
|
|
82
|
+
|
|
83
|
+
- Fields render through `Field` (label, help, error) and a widget from `widgets/registry.ts`, falling back from the resolved widget to the inferred one, then to the raw YAML field.
|
|
84
|
+
- Saves send the whole value set; the server diffs it against the file, so untouched keys never change. The body is sent only when it changed, with its original line endings and leading line breaks restored.
|
|
85
|
+
- Validation errors from the site's schema appear under the deepest field on screen and in an error summary that takes focus. Details opens when an error is inside it.
|
|
86
|
+
|
|
87
|
+
## Body editor
|
|
88
|
+
|
|
89
|
+
- `body/document.ts` turns a body into editor nodes, each tagged with its block's path (`3`, or `3.1` inside a frame), and back into items for `assembleBody`, so only what changed is rewritten: a frame's opening only when its attributes change, its children block by block, and a paragraph whose only change is in its chips by splicing the chips into its original text.
|
|
90
|
+
- `body/catalog.ts` lists the blocks the site has: its Markdoc tags, Markdoc's `partial` and `if`, and declared MDX components. `body/nodes.ts` makes the editor's node types from it: one frame type per tag with content (its content expression comes from Markdoc's `children`, so only allowed node types fit), a card, an inline chip, and the protected source block.
|
|
91
|
+
- `body/views.tsx` draws them: each frame and card is a group named after its tag, with a Settings button and its own validation messages.
|
|
92
|
+
- `body/settings.tsx` is the settings dialog: fields from the manifest rendered with the frontmatter widgets, a file picker for partials, a condition field for `if`, raw text for protected constructs, and Move up, Move down and Remove.
|
|
93
|
+
- `body/insert.tsx` is the "/" list (the text becomes a combobox while it's open) and the toolbar's Insert menu, both limited to what fits at the cursor.
|
|
94
|
+
- `body/BodyEditor.tsx` is the toolbar, the writing view, the source view, the keys, and the link and image dialogs.
|
|
95
|
+
|
|
96
|
+
Keys in the writing view: arrow onto a block to select it, Enter opens its settings, Alt+↑ and Alt+↓ move it, Delete removes it; Escape inside a frame selects the frame, and Escape again moves to the toolbar. Ctrl+Home and Ctrl+End (⌘↑ and ⌘↓ on a Mac) go to the text's ends, selecting a block that starts or ends the body. The body opens with the caret in its first text.
|
|
97
|
+
|
|
98
|
+
## Media
|
|
99
|
+
|
|
100
|
+
- `media/formats.ts` reads JPEG, PNG and WebP structure (size, EXIF orientation, location) and strips metadata without decoding. `media/hygiene.ts` decides what each upload needs: a lossless strip, a canvas re-encode (to turn a photo upright or scale it down), HEIC conversion where the browser can decode it, or SVG sanitizing with DOMPurify.
|
|
101
|
+
- `media/upload.ts` checks for a duplicate by git blob SHA (`MediaItem.hash`) before uploading. The picker and the media browser share it, and both scale uploads to the site's `images.maxUploadEdge`. The picker's uploads wait for the entry's publish in GitHub mode, so the two are one commit.
|
|
102
|
+
- `fileUrl` is asynchronous: `useFileUrl` and `FileImage` show a site file once its URL is ready.
|
|
103
|
+
|
|
104
|
+
## Focal point and crops (spec §6.6.4)
|
|
105
|
+
|
|
106
|
+
- `widgets/focal-point.tsx`: the image is one button. A click or tap sets the focal point and the marker drags; with focus on it, the arrow keys move the point 1% (Shift, 10%). "Across (%)" and "Down (%)" number fields set it without seeing or pointing, and each move is announced politely.
|
|
107
|
+
- `widgets/crops.tsx`: a preview per ratio the field lists (or every site ratio), cropped by CSS from the same `cropForRatio` the site's `<Img>` uses, captioned "Wide · 16:9" and "Custom crop" when overridden. "Adjust crop" opens a wide dialog with a fixed-ratio box (`role="application"`): drag it or a corner, or use the arrow keys (Shift resizes); Enter applies, Escape cancels, "Reset to focal point" removes the override. Each change is announced.
|
|
108
|
+
- Both only change the entry. Focal points are stored with two decimals, crops with four; the image file is never written.
|
|
109
|
+
|
|
110
|
+
## Accessibility notes (spec §6.18, layer 1)
|
|
111
|
+
|
|
112
|
+
- `a11y/useNotes.ts` runs `entryChecker` from `@vespercms/core` a moment after typing stops (the same 300 ms as body validation). The checks read bodies with micromark and Markdoc, so `a11y/run.ts` loads on first use, like the body checks.
|
|
113
|
+
- PDFs (file fields, and site PDFs linked from the body) are read once per session with PDF.js (`a11y/pdf.ts`, `pdfjs-dist`'s legacy build, its worker through a Vite `?url` import). It stays external in `dist/` and loads only when a PDF is checked.
|
|
114
|
+
- Notes render under their field (`a11y/FieldNotes.tsx`) and join the control's description; an image's alt-text notes sit beside the alt text box. The body's notes are listed under the body editor, each with Show (`body/reveal.ts` finds the block by parsing the current text again).
|
|
115
|
+
- The count in the top bar opens `a11y/NotesDrawer.tsx`: every note grouped by field in form order, with Go to field. With `a11y.blockPublishOn`, a listed rule's note refuses the save the way a body the build would fail on is refused, and the error summary links to the field.
|
|
116
|
+
- Text only: the one colour is a hairline warning border, and the words say "Accessibility note". Vesper reports problems; it never claims compliance (D19).
|
|
117
|
+
|
|
118
|
+
## Accessibility
|
|
119
|
+
|
|
120
|
+
Every screen renders through `Screen`: a skip link, a `header` with the top bar, `main` with exactly one `h1`, and focus moved to that heading after each in-app navigation. Tests in `src/**/*.test.tsx` run axe and keyboard checks in jsdom.
|