@topy-ai/maggie 0.7.52 → 0.7.54
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-zh-TW.md +28 -4
- package/README.md +93 -60
- package/bin/maggie.js +67 -12
- package/bundled-contracts/maggie-assistant-tools/booking-receptionist-example-v1.json +161 -0
- package/bundled-contracts/maggie-assistant-tools/tool-registry-v1.schema.json +268 -20
- package/bundled-contracts/maggie-deployment/bridge-host-example-v1.json +49 -7
- package/bundled-contracts/maggiedash/booking-email-templates-v1.json +181 -13
- package/bundled-contracts/maggiedash/booking-rules-v1.json +1699 -0
- package/bundled-contracts/maggiedash/stripe-booking-capabilities-v1.json +398 -39
- package/bundled-references/maggiedash-booking/PRD.md +22 -11
- package/bundled-references/maggiedash-booking/QA-TEST-PLAN.md +1 -1
- package/bundled-references/maggiedash-booking/RULES.md +167 -0
- package/bundled-references/maggiedash-booking/RUNTIME-OPERATIONS.md +2 -2
- package/bundled-references/maggiedash-booking/STRIPE-INTEGRATION.md +42 -4
- package/bundled-references/provider-adapter-contract.md +16 -3
- package/bundled-skills/catalog.json +1 -1
- package/bundled-skills/maggie-assistant-tools/SKILL.md +54 -3
- package/bundled-skills/maggie-assistant-tools/references/approvals-and-audit.md +27 -0
- package/bundled-skills/maggie-assistant-tools/references/booking-receptionist.md +45 -0
- package/bundled-skills/maggie-assistant-tools/references/bridge-host.md +54 -0
- package/bundled-skills/maggie-auth-reference/SKILL.md +18 -0
- package/bundled-skills/maggie-blog/SKILL.md +8 -0
- package/bundled-skills/maggie-booking/SKILL.md +66 -14
- package/bundled-skills/maggie-content-localization/SKILL.md +26 -0
- package/bundled-skills/maggie-dash/SKILL.md +28 -2
- package/bundled-skills/maggie-deployment/SKILL.md +48 -0
- package/bundled-skills/maggie-deployment/references/cloudflare.md +19 -0
- package/bundled-skills/maggie-deployment/references/vps.md +92 -0
- package/bundled-skills/maggie-design/SKILL.md +98 -3
- package/bundled-skills/maggie-design/references/app-ui-patterns.md +79 -0
- package/bundled-skills/maggie-feedback/SKILL.md +27 -2
- package/bundled-skills/maggie-marketplace/SKILL.md +12 -0
- package/bundled-skills/maggie-marketplace/references/directory-search.md +51 -0
- package/bundled-skills/maggie-marketplace/references/directory.mjs +112 -0
- package/bundled-skills/maggie-mobile-app/SKILL.md +65 -6
- package/bundled-skills/maggie-mobile-app/references/payments-and-push.md +78 -0
- package/bundled-skills/maggie-qa-workflow/SKILL.md +60 -0
- package/bundled-skills/maggie-seo-geo/SKILL.md +33 -0
- package/bundled-skills/maggie-service-booking/SKILL.md +28 -3
- package/bundled-skills/maggie-social-share/SKILL.md +56 -4
- package/bundled-skills/maggie-social-share/references/queue-and-connections.md +28 -0
- package/bundled-tools/clis/maggie_assistant_tools.py +84 -3
- package/bundled-tools/clis/maggie_booking.py +24 -12
- package/bundled-tools/clis/maggie_booking_connect_qa.py +136 -0
- package/bundled-tools/clis/maggie_booking_rules.py +476 -0
- package/bundled-tools/clis/maggie_bridge_host.py +77 -1
- package/bundled-tools/clis/maggie_dash.py +173 -3
- package/bundled-tools/clis/maggie_deployment_ops.py +524 -0
- package/bundled-tools/clis/maggie_design.py +15 -2
- package/bundled-tools/clis/maggie_design_bulk.py +290 -0
- package/bundled-tools/clis/maggie_design_lint.py +205 -3
- package/bundled-tools/clis/maggie_feedback.py +138 -28
- package/bundled-tools/clis/maggie_live_urls.py +138 -0
- package/bundled-tools/clis/maggie_localization.py +3 -0
- package/bundled-tools/clis/maggie_localization_keys.py +151 -0
- package/bundled-tools/clis/maggie_media_generate.py +174 -0
- package/bundled-tools/clis/maggie_mobile.py +219 -4
- package/bundled-tools/clis/maggie_product_release.py +49 -1
- package/bundled-tools/clis/maggie_qa_app_preflight.py +122 -4
- package/bundled-tools/clis/maggie_qa_fit.py +217 -0
- package/bundled-tools/clis/maggie_qa_workflow.py +5 -0
- package/bundled-tools/clis/maggie_schema_complexity.py +84 -0
- package/bundled-tools/clis/maggie_seo_routes.py +154 -0
- package/bundled-tools/clis/maggie_service_migration.py +272 -0
- package/bundled-tools/clis/maggie_social_queue.py +2 -0
- package/bundled-tools/clis/maggie_social_share.py +174 -7
- package/bundled-tools/integrations/maggie-api-pull.md +22 -0
- package/bundled-tools/runtime/assistant_approval.py +98 -0
- package/package.json +1 -1
- package/references/provider-adapter-contract.md +16 -3
- package/bundled-contracts/maggiedash/execution-board.json +0 -5463
- package/bundled-references/maggiedash-booking/CURRENT-STATE.md +0 -210
- package/bundled-references/maggiedash-booking/EXECUTION-BOARD.json +0 -5463
- package/bundled-references/maggiedash-booking/ORAWELLNESS-INTEGRATION-AUDIT.md +0 -270
- package/bundled-references/maggiedash-booking/PROGRESS.md +0 -279
- package/bundled-references/maggiedash-booking/README.md +0 -288
- package/bundled-references/maggiedash-booking/SKILLS-AND-CLI.md +0 -551
- package/bundled-references/maggiedash-booking/TASK-RUNBOOK.md +0 -107
- package/bundled-references/maggiedash-booking/TASKS.md +0 -178
|
@@ -21,9 +21,9 @@ temporary project detail, secret, or unverified guess into active memory.
|
|
|
21
21
|
|
|
22
22
|
## Contract and safety boundary
|
|
23
23
|
|
|
24
|
-
Read [
|
|
25
|
-
[
|
|
26
|
-
reference before changing code. MaggieDash owns the manager UI and versioned
|
|
24
|
+
Read the [PRD](../../bundled-references/maggiedash-booking/PRD.md), the
|
|
25
|
+
[host adapter contract](../../bundled-references/maggiedash-booking/HOST-ADAPTER.md) and the
|
|
26
|
+
task's linked reference before changing code. MaggieDash owns the manager UI and versioned
|
|
27
27
|
contracts. The host owns routes, traditional email/password sessions,
|
|
28
28
|
PostgreSQL connection/transaction lifetime, tenant authorization, provider
|
|
29
29
|
credentials, payment webhooks, notifications, and deployment. MaggieDash also
|
|
@@ -32,11 +32,12 @@ booking, customer, configuration, schedule, health, audit, hold, and lifecycle
|
|
|
32
32
|
persistence; it does not replace host connection ownership or payment/public-
|
|
33
33
|
checkout adapters.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
mark a task passed until its
|
|
39
|
-
record are complete.
|
|
35
|
+
Maintainers changing MaggieDash Booking itself work from the delivery board
|
|
36
|
+
and runbook in the Maggie source repository (`docs/maggiedash-booking/`
|
|
37
|
+
`EXECUTION-BOARD.json`, `TASK-RUNBOOK.md`, `TASKS.md`, `PROGRESS.md`); they are
|
|
38
|
+
not installed with the skill. Do not mark a task passed until its
|
|
39
|
+
implementation gate, docs update, and evidence record are complete. A host
|
|
40
|
+
project records its own evidence in its own repository.
|
|
40
41
|
|
|
41
42
|
Never put provider secrets, database credentials, payment card data, or a
|
|
42
43
|
customer export in UI props, browser bundles, fixtures, screenshots, or
|
|
@@ -76,6 +77,47 @@ recovery-rehearsal evidence together with the runtime/browser/board gates.
|
|
|
76
77
|
It applies the host-only evidence rule; fixture, test, and local environments
|
|
77
78
|
cannot satisfy a required operational release artifact.
|
|
78
79
|
|
|
80
|
+
## Booking rules every host implements the same way
|
|
81
|
+
|
|
82
|
+
[`RULES.md`](../../bundled-references/maggiedash-booking/RULES.md) fixes the rules hosts kept
|
|
83
|
+
rediscovering, each with shared test vectors
|
|
84
|
+
([`booking-rules-v1.json`](../../bundled-contracts/maggiedash/booking-rules-v1.json)):
|
|
85
|
+
the client manage-link state guard, payment after an expired hold (reinstate
|
|
86
|
+
or refund in full), venue-time schedule inputs, bigint money from Postgres,
|
|
87
|
+
till maths (add-ons already in the item price are not charged twice, service
|
|
88
|
+
charge, vouchers, packages, split tips), deposit policies only with payments
|
|
89
|
+
connected, reminder offsets with once-only markers and who-cancelled
|
|
90
|
+
notices, `.ics` escaping and per-staff feeds, availability (split hours,
|
|
91
|
+
rotas, per-staff durations, any-staff choice, gaps, groups, peak pricing,
|
|
92
|
+
DST), roles and permissions with a PIN switch that only lowers the role and
|
|
93
|
+
applies to pages, menus and actions, client safety (patch tests, blocked
|
|
94
|
+
clients, health data), loyalty and memberships, team pay, and customer
|
|
95
|
+
accounts.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
maggie booking rules-vectors # reference implementation
|
|
99
|
+
maggie booking rules-vectors --host-command "node scripts/booking-rules.mjs"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Wire `--host-command` to the host's own engine in CI: it reads one
|
|
103
|
+
`{"rule", "input"}` JSON line and prints the result.
|
|
104
|
+
|
|
105
|
+
A platform that hosts many businesses takes payments with Stripe Connect
|
|
106
|
+
direct charges on each business's account (no application fee is a valid
|
|
107
|
+
choice); see [Platform hosts](../../bundled-references/maggiedash-booking/STRIPE-INTEGRATION.md#platform-hosts-stripe-connect).
|
|
108
|
+
Prepare an end-to-end test-mode run with:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
maggie booking connect-qa --project . --detect
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
It refuses live keys, finds a test-mode connected account that can take
|
|
115
|
+
charges (read-only), prints the `stripe listen --forward-connect-to …`
|
|
116
|
+
command and lists the payment cases (deposit, 3-D Secure, decline, late
|
|
117
|
+
payment, saved card no-show fee, package, membership, refund, simulated
|
|
118
|
+
reader, dispute). Keep a hidden production smoke business for release smoke
|
|
119
|
+
bookings (RULES.md, "Release smoke business").
|
|
120
|
+
|
|
79
121
|
For local CLI regression, the sanitized no-customer fixture is
|
|
80
122
|
[`fixtures/maggiedash/booking-runtime.v1.json`](../../bundled-contracts/maggiedash/booking-runtime.v1.json).
|
|
81
123
|
|
|
@@ -86,6 +128,12 @@ Booking installer:
|
|
|
86
128
|
maggie booking install --project . --confirm
|
|
87
129
|
```
|
|
88
130
|
|
|
131
|
+
Booking is an optional MaggieDash module: this installs the core (if it is
|
|
132
|
+
not there yet) plus Booking, the same as `maggie dash add booking --confirm`.
|
|
133
|
+
Install it only when the business takes bookings — `maggie dash install`
|
|
134
|
+
alone gives a content site the core without payment or booking files, and
|
|
135
|
+
`maggie dash recommend` says whether the site looks like it sells services.
|
|
136
|
+
|
|
89
137
|
For a host project that does not already contain the Booking runtime packages,
|
|
90
138
|
let the installer add only the missing dependencies through the package manager
|
|
91
139
|
already declared by the project (`npm`, `pnpm`, `yarn`, or `bun`):
|
|
@@ -115,9 +163,12 @@ Use `--schedule systemd` or `--schedule cron` with `--ready` only to generate a
|
|
|
115
163
|
reviewable scheduler artifact; enabling it remains an operator action after
|
|
116
164
|
backup, migration, and release evidence checks.
|
|
117
165
|
|
|
118
|
-
The default installer source is
|
|
119
|
-
contains the released Astro `hostBootstrap`
|
|
120
|
-
source, including the tenant-scoped treatment
|
|
166
|
+
The default installer source is MaggieDash `main` (currently `0.2.14`; pin
|
|
167
|
+
another release with `--ref`). It contains the released Astro `hostBootstrap`
|
|
168
|
+
manifest and 47-route Booking source, including the tenant-scoped treatment
|
|
169
|
+
package catalogue, and follows the booking rules above (manage-link state
|
|
170
|
+
guard, late-payment reinstate/refund, reminder stages, who-cancelled and
|
|
171
|
+
business notices, `.ics` attachments, permission-based own-bookings scope). A fresh public-source install, `maggie booking inspect`, and
|
|
121
172
|
`astro build` are the release evidence; a local-source result alone is not.
|
|
122
173
|
|
|
123
174
|
For a new host with a reviewed `DATABASE_URL`, the explicit one-command
|
|
@@ -320,7 +371,7 @@ settings, reports, and audit. If `permissions_json` is absent on a legacy role,
|
|
|
320
371
|
apply the safe role defaults server-side; never treat a missing object as full
|
|
321
372
|
read access. Explicitly granted staff capabilities must be honored by the
|
|
322
373
|
route handler, while staff mutations remain limited to approved lifecycle
|
|
323
|
-
transitions. Keep the access contract, Astro host, reference host,
|
|
374
|
+
transitions. Keep the access contract, Astro host, reference host, installed host copies,
|
|
324
375
|
and CLI validator synchronized whenever a route capability changes.
|
|
325
376
|
The bootstrap now uses the host's server-side Resend configuration to send
|
|
326
377
|
single-use invitation and password-reset links; raw tokens are never stored.
|
|
@@ -475,8 +526,9 @@ Follow the task board rather than building a disconnected calendar first:
|
|
|
475
526
|
6. Add the provider-neutral payment port and a test-mode adapter. Reconcile
|
|
476
527
|
signed duplicate/out-of-order webhooks before enabling capture.
|
|
477
528
|
7. Run the UI source gate, contract gate, host runtime conformance, browser
|
|
478
|
-
journeys, and release gate. Record each command in
|
|
479
|
-
|
|
529
|
+
journeys, and release gate. Record each command in the host project's own
|
|
530
|
+
progress log (maintainers: `docs/maggiedash-booking/PROGRESS.md` in the
|
|
531
|
+
Maggie source repository).
|
|
480
532
|
|
|
481
533
|
8. Verify the scheduled worker queues and delivers a reminder once for a
|
|
482
534
|
confirmed booking inside the 24-hour window. Check the notification
|
|
@@ -70,6 +70,32 @@ Norwegian, Finnish, Greek, Hebrew, Czech, Romanian, Hungarian, Thai, Persian,
|
|
|
70
70
|
and Swahili. Use BCP 47 regions such as `es-MX`, `pt-BR`, `ar-SA`, or `ja-JP`
|
|
71
71
|
when market-specific copy is required.
|
|
72
72
|
|
|
73
|
+
## Text keys are site-wide; generated pages need position keys
|
|
74
|
+
|
|
75
|
+
A translation stored under its source text (a text hash) applies to **every
|
|
76
|
+
page** that shows that text. That is right for a shared header and wrong for
|
|
77
|
+
generic phrases on generated pages: "your practice" is 诊所 on a dental page
|
|
78
|
+
and 事务所 on an accountants page, and only one translation can exist per
|
|
79
|
+
text. For generated or templated pages (one page per industry, city or
|
|
80
|
+
product), render the source page, then store each node's translation under a
|
|
81
|
+
**per-page position key** (page + node position, with the source text hash so
|
|
82
|
+
a changed source shows as stale). Keep text keys only as a fallback for text
|
|
83
|
+
that is genuinely the same everywhere.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
maggie localization key-check rows.json [--locale zh-hans]
|
|
87
|
+
maggie localization fragments --html dist --brand "Maggie"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`key-check` reads per-page rows (`page`, `position`, `text`, `translation`)
|
|
91
|
+
and lists source texts translated differently on different pages — each one
|
|
92
|
+
needs position keys. `fragments` reads rendered HTML and lists text split
|
|
93
|
+
around an inline brand span into one- or two-word pieces ("Can", "No.",
|
|
94
|
+
"What") that cannot be translated alone; translate the whole sentence with
|
|
95
|
+
the span as a placeholder instead. Agents writing in parallel must use one
|
|
96
|
+
glossary for short shared fragments, or the same piece gets different
|
|
97
|
+
translations from different writers.
|
|
98
|
+
|
|
73
99
|
## Email and lifecycle email
|
|
74
100
|
|
|
75
101
|
Localized email is a content surface with its own checks. Resolve the
|
|
@@ -31,9 +31,35 @@ must already be configured for the project owner.
|
|
|
31
31
|
maggie dash install --project . --confirm
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
This installs **only the core**: the dashboard, the auth boundary
|
|
35
|
+
(email/password sign-in, first-owner registration, password reset), the
|
|
36
|
+
`/_maggie/admin` route, navigation and the core schema. It then reads the host
|
|
37
|
+
(framework, existing sign-in, database, payments, whether the site sells
|
|
38
|
+
services or publishes a blog, locales) and prints `recommendations`, one per
|
|
39
|
+
optional module or companion skill with a reason. Install a module only after
|
|
40
|
+
the user agrees:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
maggie dash recommend --project . # analysis only, writes nothing
|
|
44
|
+
maggie dash add booking --project . --confirm # same as: maggie booking install --confirm
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Booking is the optional code module MaggieDash ships (workspace, backend,
|
|
48
|
+
schema, Stripe adapter, worker, env example). Blog, localization, social
|
|
49
|
+
sharing and ops are Maggie skills that use the core panels; the
|
|
50
|
+
recommendations name them. Never pass `--with booking` or `--all-modules` for
|
|
51
|
+
a site that has not asked for bookings: a content-only site must not receive
|
|
52
|
+
payment and booking files. A release without the module split (MaggieDash
|
|
53
|
+
0.2.13 or older) still installs everything and says so in `notices`.
|
|
54
|
+
|
|
55
|
+
Server settings in the Astro scaffold are read at run time
|
|
56
|
+
(`src/lib/maggie/env.ts`). Never read a secret through `import.meta.env.X` in
|
|
57
|
+
host code: Vite inlines it into the build output.
|
|
58
|
+
|
|
34
59
|
The command is idempotent, preserves existing files unless `--force` is
|
|
35
|
-
explicitly supplied, and records the resolved revision
|
|
36
|
-
`.maggie/dash-install.json
|
|
60
|
+
explicitly supplied, and records the resolved revision and installed
|
|
61
|
+
`modules` in `.maggie/dash-install.json` (a later `install` keeps refreshing
|
|
62
|
+
modules that were added). It never accepts or stores a GitHub token as a
|
|
37
63
|
CLI argument. For local development or a pinned checkout, use
|
|
38
64
|
`--source /path/to/MaggieDash` or `--source URL --ref REF`.
|
|
39
65
|
|
|
@@ -17,9 +17,41 @@ metadata:
|
|
|
17
17
|
- A multi-tenant assistant bridge host on a VPS is declared and checked with
|
|
18
18
|
`maggie deployment bridge-plan`, `bridge-collect-script` and `bridge-check`;
|
|
19
19
|
see [bridge host](../maggie-assistant-tools/references/bridge-host.md).
|
|
20
|
+
Declare `workloads` (`chat`, `photos`, `image-generation`) and
|
|
21
|
+
`network.proxyLimits`; `bridge-check` reads the effective nginx
|
|
22
|
+
`client_max_body_size` / `proxy_read_timeout` of the server block that
|
|
23
|
+
proxies to the bridge port and fails below 25m / 360 s for photos and image
|
|
24
|
+
jobs (a 512k / 300 s proxy cut them off).
|
|
20
25
|
- Product CLI and MCP packages: `maggie product release-check|smoke|mcp-check`
|
|
21
26
|
([product release](../maggie-assistant-tools/references/product-release.md)).
|
|
22
27
|
|
|
28
|
+
## Downstream surfaces after a major product release
|
|
29
|
+
|
|
30
|
+
A product change is not released while an agent-facing surface still
|
|
31
|
+
describes the old product. After any major change to the app, the dashboard or
|
|
32
|
+
the backend API (a new feature or channel, a renamed tab, a changed rule or
|
|
33
|
+
limit), walk every surface below and either update it or record "no change":
|
|
34
|
+
|
|
35
|
+
| Surface | Check | Evidence |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| Remote MCP tools | New or changed API params and response fields have tools; descriptions use the product's words | `tools/list` on the live server: count and the new names |
|
|
38
|
+
| Agent system prompts | In-app assistant and any hosted bridge/agent prompt name tabs, channels and rules as the app does | One question about the change answered correctly |
|
|
39
|
+
| Skills catalog and marketplace | Grep skills for the old wording; `mcp_tools` lists only real tool names; any tool-name allowlist is in sync with the tool source | Validator passes; marketplace repo pushed; agent hosts re-synced and recycled |
|
|
40
|
+
| CLI and MCP npm packages | Commands/columns for the new data; both package versions bumped together | `npm view <pkg> version` |
|
|
41
|
+
| Third-party plugins (ChatGPT, IDE) | Usually nothing if they use the remote MCP; re-review when tool names or auth change | Plugin lists the new tools |
|
|
42
|
+
| SDK / API client types | New response fields typed | Workspace typecheck |
|
|
43
|
+
| Mobile app | Web deploy plus OTA when the native fingerprint matches; a new build when it does not | Fingerprint compare; update id |
|
|
44
|
+
| Assistant memory | A new channel or surface gets its own memory channel | Memory read for that channel |
|
|
45
|
+
| Docs | PRD task list, progress log and the deployment runbook | — |
|
|
46
|
+
| Preview host protection | Before (or after) adding basic auth to a preview host, every workflow, script or webhook that sends to it is repointed to production or sends the preview's basic-auth credentials from a secret — a bearer token does not pass basic auth | `maggie deployment preview-auth-check --host preview.example.com --repo . --live` passes; the next run of each listed workflow is green and the public surfaces it feeds show the new version |
|
|
47
|
+
| npm staged publishes | A just-published version can stay invisible as `latest` for minutes; do not report it until it is. `Cannot publish over previously staged version` means the earlier attempt was already accepted, not a failure | `maggie product published --package-name NAME --version X --publish-log publish.log` reports `live` |
|
|
48
|
+
|
|
49
|
+
Run the walk in the release order (schema → backend → dashboard → app →
|
|
50
|
+
MCP/agent → bridge → npm → skills) and record which surfaces changed in the
|
|
51
|
+
release record. Keep publish tokens in an env file and pass them through a
|
|
52
|
+
temporary npmrc (`NPM_CONFIG_USERCONFIG`) that is deleted afterwards; never
|
|
53
|
+
print them.
|
|
54
|
+
|
|
23
55
|
## Edge and rendered canary
|
|
24
56
|
|
|
25
57
|
After staging or production deployment, verify that the edge serves the
|
|
@@ -562,6 +594,12 @@ is on a request path; follow [cloudflare.md](references/cloudflare.md) to move
|
|
|
562
594
|
the database to D1 and media to R2/Cloudflare Images or an explicitly selected
|
|
563
595
|
external adapter.
|
|
564
596
|
|
|
597
|
+
Run `maggie deployment cloudflare-preflight --env production --env-file .env
|
|
598
|
+
--verify-zone` before every `wrangler deploy`: an empty
|
|
599
|
+
`CLOUDFLARE_API_TOKEN`/`CLOUDFLARE_ACCOUNT_ID` makes wrangler fall back to its
|
|
600
|
+
cached OAuth login, possibly another account, and upload a stray Worker
|
|
601
|
+
there.
|
|
602
|
+
|
|
565
603
|
## VPS target
|
|
566
604
|
|
|
567
605
|
Use `--target vps` for direct VPS hosting or
|
|
@@ -571,6 +609,16 @@ immutable releases, the `current` symlink, staging-first rollout, HTTPS
|
|
|
571
609
|
verification, and rollback. Remote checks remain pending until the host,
|
|
572
610
|
credentials, and explicit deployment confirmation are provided.
|
|
573
611
|
|
|
612
|
+
For an SSR Node app with release folders, `maggie deployment vps-release`
|
|
613
|
+
runs the whole release as one reviewed command (dry run by default): archive
|
|
614
|
+
of a commit, install and build as the deploy user with the server env file,
|
|
615
|
+
atomic switch, restart, health with warm-up, prune, roadmap line. Before a
|
|
616
|
+
surgical copy to several hosts, `maggie deployment file-parity` proves every
|
|
617
|
+
host still holds the deployed commit's files; `maggie deployment warm-check`
|
|
618
|
+
judges latency only after warm-up requests. Failover between hosts must
|
|
619
|
+
never replay a write: see "Failover must not replay writes" in
|
|
620
|
+
[vps.md](references/vps.md).
|
|
621
|
+
|
|
574
622
|
## Provider boundary
|
|
575
623
|
|
|
576
624
|
Deployment providers implement the same conceptual operations:
|
|
@@ -45,6 +45,7 @@ From the Astro starter:
|
|
|
45
45
|
npm run build:cloudflare
|
|
46
46
|
npx wrangler d1 migrations apply DB --local
|
|
47
47
|
npx wrangler dev
|
|
48
|
+
maggie deployment cloudflare-preflight --env production --env-file .env --verify-zone
|
|
48
49
|
npx wrangler deploy --env staging
|
|
49
50
|
npx wrangler deploy --env production
|
|
50
51
|
```
|
|
@@ -74,3 +75,21 @@ After deploy, check:
|
|
|
74
75
|
- D1 migration version, R2/media delivery, KV/session behavior, API Pull,
|
|
75
76
|
rewrite queue, and scheduled triggers are observable;
|
|
76
77
|
- staging has no production analytics or email recipients.
|
|
78
|
+
|
|
79
|
+
## Deploy preflight: never fall back to another account
|
|
80
|
+
|
|
81
|
+
When `CLOUDFLARE_API_TOKEN` or `CLOUDFLARE_ACCOUNT_ID` is empty — for example
|
|
82
|
+
a deploy script reading `CF_API_TOKEN` from `.env` — wrangler silently uses its
|
|
83
|
+
cached OAuth login, which may belong to another Cloudflare account. It uploads
|
|
84
|
+
a stray Worker there and then fails with `Can't infer zone from route ...
|
|
85
|
+
[code: 10082]`; the live site keeps the old build and the stray Worker has to
|
|
86
|
+
be deleted by hand.
|
|
87
|
+
|
|
88
|
+
`maggie deployment cloudflare-preflight` fails before `wrangler deploy` when
|
|
89
|
+
either variable is empty (from the environment or `--env-file`), names
|
|
90
|
+
near-miss variables that are set instead, warns about a cached OAuth login,
|
|
91
|
+
fails when the config's `account_id` differs from `CLOUDFLARE_ACCOUNT_ID`, and
|
|
92
|
+
with `--verify-zone` checks with read-only API calls that the token verifies
|
|
93
|
+
and that the account owns the zone of every route. Put `account_id` in
|
|
94
|
+
`wrangler.jsonc` so wrangler itself refuses another account, and run
|
|
95
|
+
`wrangler logout` on machines that only deploy with tokens.
|
|
@@ -155,6 +155,35 @@ inspect → plan → backup/migrate → npm ci → npm run build
|
|
|
155
155
|
Deploy staging first. A passing build does not prove Nginx, systemd, DNS, TLS,
|
|
156
156
|
robots, sitemap, or private-route correctness.
|
|
157
157
|
|
|
158
|
+
### One command per release
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
maggie deployment vps-release --host deploy@203.0.113.10 \
|
|
162
|
+
--release-root /srv/site --service site --env-file /etc/site.env \
|
|
163
|
+
--subdir app --ref main --health-url https://www.example.com/ \
|
|
164
|
+
--health-url https://www.example.com/sitemap.xml # dry run
|
|
165
|
+
# review the printed remote script, then add --confirm
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
It names the release `<UTC timestamp>-<commit>` (never reused), uploads a
|
|
169
|
+
`git archive` of that commit (no local build artefacts), installs and builds
|
|
170
|
+
on the host **as the deploy user** with the server env file loaded, refuses
|
|
171
|
+
files owned by anyone else, switches `current` atomically, restarts through
|
|
172
|
+
the narrow sudoers rule, checks each health URL (retrying only 000/502/503/504)
|
|
173
|
+
and then sends `--warm` requests before timing one, prunes to `--keep`
|
|
174
|
+
releases, and prints a roadmap line. `--install`, `--build` and `--restart`
|
|
175
|
+
override the defaults (`npm ci`, `npm run build`,
|
|
176
|
+
`sudo -n systemctl restart <service>`).
|
|
177
|
+
|
|
178
|
+
**Build as the deploy user, never with sudo.** `sudo bash -c 'set -a; .
|
|
179
|
+
/etc/app.env; npx astro build'` leaves root-owned `dist/` and cache files;
|
|
180
|
+
the retention prune then fails with "Permission denied" and the narrow sudoers
|
|
181
|
+
contract (systemctl only) cannot remove them. Make the env file readable by
|
|
182
|
+
the deploy group (`chgrp deploy /etc/app.env; chmod 640 /etc/app.env`)
|
|
183
|
+
instead. `vps-release` refuses to run as root, refuses an unreadable env
|
|
184
|
+
file, and reports releases it cannot prune with the one-time fix
|
|
185
|
+
(`sudo chown -R deploy: <release>`).
|
|
186
|
+
|
|
158
187
|
### Release retention
|
|
159
188
|
|
|
160
189
|
The retention policy is one explicit number, `--keep-releases N` (default 2),
|
|
@@ -198,6 +227,69 @@ maggie deployment secret-parity \
|
|
|
198
227
|
|
|
199
228
|
Run it after adding a secret and before enabling a host in a pool.
|
|
200
229
|
|
|
230
|
+
### Surgical multi-host copies
|
|
231
|
+
|
|
232
|
+
When a fix copies a few built files to several hosts instead of a full
|
|
233
|
+
release, first prove every host still holds the copies of the deployed
|
|
234
|
+
commit, so the copy does not hide a hand edit or a half-finished earlier
|
|
235
|
+
deploy:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
maggie deployment file-parity --host api-1 --host api-2 --host api-3 \
|
|
239
|
+
--root /srv/api/current --subdir api --ref <deployed commit> \
|
|
240
|
+
--path dist/server/entry.mjs --path dist/server/chunks/routes.mjs
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Each host's files are hashed over ssh (stdin script, no quoting through
|
|
244
|
+
ssh) and compared with `git show <ref>:<path>` and with the other hosts; a
|
|
245
|
+
file that differs or is missing fails, a file that does not exist at the ref
|
|
246
|
+
is reported as `new-file`.
|
|
247
|
+
|
|
248
|
+
### Judge latency after warm-up
|
|
249
|
+
|
|
250
|
+
The first requests after a restart start the workers (often 8-16 s with
|
|
251
|
+
several workers). Warm each host before deciding a release is slow:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
maggie deployment warm-check --url https://api.example.com/health --warm 4 --samples 5 --budget-ms 800
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
`--warm` should be at least the number of worker processes; only the later
|
|
258
|
+
samples are judged.
|
|
259
|
+
|
|
260
|
+
### Failover must not replay writes
|
|
261
|
+
|
|
262
|
+
An edge or proxy with ordered upstreams may retry the next host when one is
|
|
263
|
+
down. Retrying is only safe when the first host never ran the request:
|
|
264
|
+
|
|
265
|
+
- Fail over on **connection errors and timeouts before a response**, and on
|
|
266
|
+
502/503/504 **only when the body is the gateway's own error page**
|
|
267
|
+
(HTML/non-JSON from the proxy). An application's JSON 5xx ("the AI plan
|
|
268
|
+
failed") is the app's answer: relay it to the client.
|
|
269
|
+
- **Never re-send a non-idempotent request** (POST/PATCH/DELETE) to another
|
|
270
|
+
upstream unless it carries an `Idempotency-Key` the app honours. nginx does
|
|
271
|
+
not retry non-idempotent requests unless `proxy_next_upstream
|
|
272
|
+
non_idempotent` is set — do not set it; keep `proxy_next_upstream error
|
|
273
|
+
timeout` for writes.
|
|
274
|
+
- Let the app mark its own errors (for example `X-App-Error: 1` or
|
|
275
|
+
`Content-Type: application/json`) so the edge can tell them apart.
|
|
276
|
+
|
|
277
|
+
A JSON 502 treated as "upstream down" replayed a create on a second host and
|
|
278
|
+
left an orphan record per failover.
|
|
279
|
+
|
|
280
|
+
### Local servers in checks
|
|
281
|
+
|
|
282
|
+
Start local servers with a pid file and stop them by pid; never
|
|
283
|
+
`pkill -f '<pattern>'` from the same shell command — the pattern also matches
|
|
284
|
+
the shell running the command, which kills it, and later steps silently do
|
|
285
|
+
not run:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
node dist/server/entry.mjs & echo $! > .maggie/server.pid
|
|
289
|
+
# ... checks ...
|
|
290
|
+
kill "$(cat .maggie/server.pid)" && rm -f .maggie/server.pid
|
|
291
|
+
```
|
|
292
|
+
|
|
201
293
|
### Environment isolation
|
|
202
294
|
|
|
203
295
|
A local `.env` must never point at the production database. Shared databases
|
|
@@ -262,6 +262,59 @@ maggie design step author-<id> --project . \
|
|
|
262
262
|
An author plan is not complete until every `requiredSteps` entry has matching
|
|
263
263
|
evidence and the explicit approval step is recorded.
|
|
264
264
|
|
|
265
|
+
### Bulk author mode (many similar pages)
|
|
266
|
+
|
|
267
|
+
For a family of similar pages (one use-case page per industry, one page per
|
|
268
|
+
city), use one bulk job instead of hand-built scripts:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
maggie design bulk plan --project . --targets targets.json --brief BRIEF.md --facts FACTS.md \
|
|
272
|
+
--validator '["python3", "scripts/validate-page.py"]' --check links --check prices --confirm
|
|
273
|
+
maggie design bulk next <job> --stage write # next batch (default 8) with each page's currentHash
|
|
274
|
+
maggie design bulk record <job> --target ID --stage written --expect-hash HASH
|
|
275
|
+
maggie design bulk validate <job> # project validator + built-in checks
|
|
276
|
+
maggie design bulk next <job> --stage picture # only pages validated and unchanged since
|
|
277
|
+
maggie design bulk record <job> --target ID --stage pictured
|
|
278
|
+
maggie design bulk status <job>
|
|
279
|
+
maggie design bulk seed <job> --confirm # only when every page passed
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
The job enforces what goes wrong in a parallel run: a picture is drawn only
|
|
283
|
+
for a page that validated and has not changed since (a picture worker must
|
|
284
|
+
never rewrite a page a writer is still editing); a writer records with the
|
|
285
|
+
hash it read and is refused if the page changed meanwhile (re-read, re-apply,
|
|
286
|
+
record again); changing the brief, facts or validator mid-run sends every
|
|
287
|
+
earlier pass back to `revalidate`; nothing is seeded until all pages are
|
|
288
|
+
validated and pictured. Built-in checks: every internal link points at a page
|
|
289
|
+
of the job or a `--known-routes` entry, and every price appears in the facts
|
|
290
|
+
file. The project validator checks structure, counts, facts and translation
|
|
291
|
+
coverage. Share one glossary between writers for short repeated fragments
|
|
292
|
+
(see maggie-content-localization, position keys).
|
|
293
|
+
|
|
294
|
+
### Page pictures: choose the engine before a large run
|
|
295
|
+
|
|
296
|
+
`maggie media generate` draws one picture with either engine; `maggie media
|
|
297
|
+
engine-plan` states time and cost first. Show the plan and wait for a yes
|
|
298
|
+
before a run of more than a handful of pictures.
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
maggie media engine-plan --count 510 --size 2400x1000 --gemini-unit-cost <price>
|
|
302
|
+
maggie media generate --engine gemini --size 2400x1000 --prompt "..." --output hero.png --confirm
|
|
303
|
+
maggie media generate --engine codex --image screen.png --prompt "Redraw as a clean step picture" --output step.png --confirm
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
| Engine | Billing | Speed (observed) | Reference pictures |
|
|
307
|
+
|---|---|---|---|
|
|
308
|
+
| `codex` | the ChatGPT plan's quota via the local `codex` CLI, no per-picture charge | about 75 s | yes (`--image`, before `--`) |
|
|
309
|
+
| `gemini` | the project's own `GEMINI_API_KEY` (its Google account) | about 19 s at 2K | no |
|
|
310
|
+
|
|
311
|
+
For 510 pictures that is hours versus minutes. Gemini takes the nearest
|
|
312
|
+
aspect ratio and a size from the target (2400x1000 → 21:9 at 2K; 1K up to
|
|
313
|
+
1300 px, 2K up to 2600 px, else 4K); crop to the exact size and convert to
|
|
314
|
+
WebP afterwards. `engine-plan` leaves the Gemini cost empty until you pass
|
|
315
|
+
`--gemini-unit-cost` from the current price list. Only transient Gemini
|
|
316
|
+
errors (429, 5xx, network) are retried.
|
|
317
|
+
|
|
265
318
|
## Content UI initialization
|
|
266
319
|
|
|
267
320
|
Use content UI initialization when `maggie-blog` or
|
|
@@ -333,7 +386,13 @@ the project's auth or device-capability implementation:
|
|
|
333
386
|
- `--viewport tablet=768x1024` overrides a plan viewport; the evidence and
|
|
334
387
|
screenshots must then match it.
|
|
335
388
|
- `--pattern` attaches acceptance rules from
|
|
336
|
-
[app UI patterns](references/app-ui-patterns.md) to the QA scenarios
|
|
389
|
+
[app UI patterns](references/app-ui-patterns.md) to the QA scenarios:
|
|
390
|
+
`tabbed-modal`, `editor-sheet-assistant`, `usage-hero`, `site-switcher`,
|
|
391
|
+
`background-job` (slow media generation: a job per workspace, Stop or
|
|
392
|
+
replace, one toast), `chat-composer` (one-line auto-grow composer), and the
|
|
393
|
+
no-page-scroll dashboard set `hash-tabs`, `right-drawer`,
|
|
394
|
+
`two-column-sheet`, `screen-pager` (check those with `maggie qa
|
|
395
|
+
fit-check`).
|
|
337
396
|
|
|
338
397
|
Expo Router files (`app/<route>.tsx`, `app/<route>/index.tsx`) are detected as
|
|
339
398
|
the route source. The plan never collects credentials or copies private data.
|
|
@@ -385,7 +444,40 @@ device or simulator screenshot.
|
|
|
385
444
|
|
|
386
445
|
Keep `maggie design pressable-check` in the same loop for React Native Web
|
|
387
446
|
apps: together they caught nested pressables and shell regressions during
|
|
388
|
-
repeated sheet and modal changes.
|
|
447
|
+
repeated sheet and modal changes. Add `maggie design textinput-check`: a
|
|
448
|
+
`multiline` `TextInput` renders a 2-row `<textarea>` on web unless it has
|
|
449
|
+
`numberOfLines` or an explicit height, and auto-grow driven only by
|
|
450
|
+
`onContentSizeChange` grows but never shrinks on web (measure `scrollHeight`
|
|
451
|
+
after collapsing; see the `chat-composer` pattern). `// maggie-allow-textinput`
|
|
452
|
+
marks a reviewed exception.
|
|
453
|
+
|
|
454
|
+
Closing a modal sheet is the third iOS-only trap. Closing a sheet and, in the
|
|
455
|
+
same tick, showing a toast, refreshing or writing query data, navigating,
|
|
456
|
+
opening another sheet, or changing what the closing sheet shows can leave the
|
|
457
|
+
sheet's transparent window on top of the app: it looks fine but takes no
|
|
458
|
+
touches until the app is killed. Close first, then do the rest once it has
|
|
459
|
+
slid away:
|
|
460
|
+
|
|
461
|
+
export const SHEET_CLOSE_MS = 450; // the slide-out duration
|
|
462
|
+
export function afterSheetCloses(fn: () => void) {
|
|
463
|
+
if (Platform.OS === "web") fn(); else setTimeout(fn, SHEET_CLOSE_MS);
|
|
464
|
+
}
|
|
465
|
+
// setSheet(null); afterSheetCloses(() => { toast(...); refresh(); });
|
|
466
|
+
|
|
467
|
+
maggie design sheet-check --project apps/app
|
|
468
|
+
|
|
469
|
+
`sheet-check` finds closes (`setSheet(null)`, `setXOpen(false)`, `onClose()`,
|
|
470
|
+
and the setter of any state used in a sheet's `open`/`visible` prop) followed
|
|
471
|
+
in the same block by a toast, a refetch/invalidate/`setQueryData`,
|
|
472
|
+
`router.*`/`*.open(…)` or another sheet opening; work inside
|
|
473
|
+
`afterSheetCloses`, `setTimeout`, `requestAnimationFrame`, a local helper that
|
|
474
|
+
wraps them, or after `await new Promise(r => setTimeout(r, …))` is deferred and
|
|
475
|
+
passes. Errors fail the check. Warnings: a parent callback (`onDone()`) right
|
|
476
|
+
after a close, and a sheet whose content reads the same state as its `open`
|
|
477
|
+
prop (`open={Boolean(item)} title={item?.label}`), which empties the sheet
|
|
478
|
+
while it slides away — keep a separate open flag. `// maggie-allow-sheet-close`
|
|
479
|
+
marks a reviewed exception. Keep it with pressable-check, safe-area-check and
|
|
480
|
+
textinput-check in the app's lint loop.
|
|
389
481
|
|
|
390
482
|
### Onboarding wizards
|
|
391
483
|
|
|
@@ -719,7 +811,10 @@ It runs only when an `@import "tailwindcss"` entry exists (or with
|
|
|
719
811
|
`--tailwind-v4`), honours `@layer` blocks and `layer(...)` imports, and fails on
|
|
720
812
|
unlayered element or icon rules that set utility-competing properties.
|
|
721
813
|
Unlayered `html`/`body` rules and class rules that set `display` are reported
|
|
722
|
-
as warnings.
|
|
814
|
+
as warnings. In every project (Tailwind or not) it also warns about
|
|
815
|
+
`width: max-content` rules and `w-max` classes without `max-width`/overflow
|
|
816
|
+
handling: a tab strip or toolbar sized to its content makes a 390 px page
|
|
817
|
+
scroll sideways — give it `overflow-x: auto` inside a `min-width: 0` parent. Keep `css-cascade-check` for the rendered-browser proof.
|
|
723
818
|
|
|
724
819
|
### Palette role contract
|
|
725
820
|
|
|
@@ -64,3 +64,82 @@ The first block of an inbox or account screen when usage limits matter.
|
|
|
64
64
|
accessible name.
|
|
65
65
|
- Account-level actions (add a site) are visually separated from per-site
|
|
66
66
|
rows, and are audited at account scope, not under the open site.
|
|
67
|
+
|
|
68
|
+
## `background-job` — slow media generation as a background job
|
|
69
|
+
|
|
70
|
+
Image or video generation can take 1-2 minutes (more on a user's own
|
|
71
|
+
ChatGPT plan). Never run it inside the request that started it.
|
|
72
|
+
|
|
73
|
+
- **Start returns a job**: `POST .../generate` answers at once with
|
|
74
|
+
`{job: {id, itemId, startedAt}}`; the work runs in the background.
|
|
75
|
+
- **One job per workspace** (or per user): a second start answers
|
|
76
|
+
`409 {busy: {jobId, itemId}}`; the UI asks "Stop it and start this one?"
|
|
77
|
+
and, on yes, calls start with `replace: true`, which interrupts the old job.
|
|
78
|
+
- **State carries the job**: the item/list GET includes the running job, so
|
|
79
|
+
a reload, a second device or another tab shows it. Poll that GET every few
|
|
80
|
+
seconds while a job runs (no extra polling endpoint); stop polling when it
|
|
81
|
+
ends.
|
|
82
|
+
- **On the item**: a spinner over its thumbnail or preview, and the same
|
|
83
|
+
action button becomes **Stop**.
|
|
84
|
+
- **One toast** on done or failed (with the reason, e.g. "Your ChatGPT image
|
|
85
|
+
limit is used up"), never one per poll.
|
|
86
|
+
- **Stale jobs expire** (for example after 10 minutes without progress) so a
|
|
87
|
+
crashed worker never locks the workspace.
|
|
88
|
+
|
|
89
|
+
## `chat-composer` — one-line auto-grow composer (React Native Web)
|
|
90
|
+
|
|
91
|
+
A `multiline` `TextInput` on web renders a `<textarea>` with the browser's
|
|
92
|
+
default 2 rows, so an empty composer is ~56 px tall with the placeholder high
|
|
93
|
+
and 40 px buttons low.
|
|
94
|
+
|
|
95
|
+
- Give it an explicit single-line height (`numberOfLines={1}` or a
|
|
96
|
+
`style.height` equal to one line plus padding) and centre the row items.
|
|
97
|
+
- Auto-grow on web by measuring: set the height to the minimum, read
|
|
98
|
+
`scrollHeight`, then set `min(scrollHeight, max)`. `onContentSizeChange`
|
|
99
|
+
never reports a smaller size on web, so a composer driven only by it grows
|
|
100
|
+
but never shrinks after text is deleted.
|
|
101
|
+
- Align send/attach buttons to the bottom (`alignItems: "flex-end"`) so they
|
|
102
|
+
stay on the last line as it grows.
|
|
103
|
+
- `maggie design textinput-check` flags multiline inputs without a height and
|
|
104
|
+
`onContentSizeChange`-only auto-grow.
|
|
105
|
+
|
|
106
|
+
## Web dashboards that never scroll the page
|
|
107
|
+
|
|
108
|
+
For dense desktop dashboards (settings, bookings, reports) where the owner
|
|
109
|
+
wants no page scroll at a laptop viewport (1536x687 with browser chrome)
|
|
110
|
+
and a usable 390 px phone layout. Check with `maggie qa fit-check`.
|
|
111
|
+
|
|
112
|
+
### `hash-tabs` — tabs that keep state in the URL hash
|
|
113
|
+
|
|
114
|
+
- `role="tablist"` / `role="tab"` / `role="tabpanel"`; the active tab is
|
|
115
|
+
`#tab-id` in the URL, restored on load and on `hashchange` (back/forward).
|
|
116
|
+
- Emit a change event (for example `tabs:change` with the tab id) so
|
|
117
|
+
components inside a panel can re-measure when they become visible — a
|
|
118
|
+
hidden panel measures zero.
|
|
119
|
+
- The tab strip scrolls sideways on phones (`overflow-x: auto`,
|
|
120
|
+
`min-width: 0` on the flex parent); never `width: max-content` without a
|
|
121
|
+
scroll container (`maggie design layer-check` warns).
|
|
122
|
+
|
|
123
|
+
### `right-drawer` — details and forms in a drawer
|
|
124
|
+
|
|
125
|
+
- A `role="dialog"` panel from the right (`min(480px, 100vw)` wide, full
|
|
126
|
+
height) that owns its scroll; the page behind does not scroll.
|
|
127
|
+
- Disclosures with a lot of content ("Edit opening hours") open as a drawer
|
|
128
|
+
instead of expanding the page.
|
|
129
|
+
- Escape, the close button and the backdrop close it and return focus to the
|
|
130
|
+
opener.
|
|
131
|
+
|
|
132
|
+
### `two-column-sheet` — large modal with section quick-nav
|
|
133
|
+
|
|
134
|
+
- A large modal (about 1100 px, max 90 vh) for long settings: sections in two
|
|
135
|
+
columns from 1024 px, one column below; the body is the only scroll owner.
|
|
136
|
+
- A quick-nav (chips or a side list) scrolls the sheet to each section and
|
|
137
|
+
marks the current one.
|
|
138
|
+
|
|
139
|
+
### `screen-pager` — page size from the viewport
|
|
140
|
+
|
|
141
|
+
- The page size is the number of rows that fit the list's visible height;
|
|
142
|
+
re-measure on resize and on `tabs:change` when the list sits in a tab.
|
|
143
|
+
- Use it only for client-side lists. When the server already pages, use the
|
|
144
|
+
server pager — two pagers on one list showed half pages and double
|
|
145
|
+
"next" buttons.
|