@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.
Files changed (79) hide show
  1. package/README-zh-TW.md +28 -4
  2. package/README.md +93 -60
  3. package/bin/maggie.js +67 -12
  4. package/bundled-contracts/maggie-assistant-tools/booking-receptionist-example-v1.json +161 -0
  5. package/bundled-contracts/maggie-assistant-tools/tool-registry-v1.schema.json +268 -20
  6. package/bundled-contracts/maggie-deployment/bridge-host-example-v1.json +49 -7
  7. package/bundled-contracts/maggiedash/booking-email-templates-v1.json +181 -13
  8. package/bundled-contracts/maggiedash/booking-rules-v1.json +1699 -0
  9. package/bundled-contracts/maggiedash/stripe-booking-capabilities-v1.json +398 -39
  10. package/bundled-references/maggiedash-booking/PRD.md +22 -11
  11. package/bundled-references/maggiedash-booking/QA-TEST-PLAN.md +1 -1
  12. package/bundled-references/maggiedash-booking/RULES.md +167 -0
  13. package/bundled-references/maggiedash-booking/RUNTIME-OPERATIONS.md +2 -2
  14. package/bundled-references/maggiedash-booking/STRIPE-INTEGRATION.md +42 -4
  15. package/bundled-references/provider-adapter-contract.md +16 -3
  16. package/bundled-skills/catalog.json +1 -1
  17. package/bundled-skills/maggie-assistant-tools/SKILL.md +54 -3
  18. package/bundled-skills/maggie-assistant-tools/references/approvals-and-audit.md +27 -0
  19. package/bundled-skills/maggie-assistant-tools/references/booking-receptionist.md +45 -0
  20. package/bundled-skills/maggie-assistant-tools/references/bridge-host.md +54 -0
  21. package/bundled-skills/maggie-auth-reference/SKILL.md +18 -0
  22. package/bundled-skills/maggie-blog/SKILL.md +8 -0
  23. package/bundled-skills/maggie-booking/SKILL.md +66 -14
  24. package/bundled-skills/maggie-content-localization/SKILL.md +26 -0
  25. package/bundled-skills/maggie-dash/SKILL.md +28 -2
  26. package/bundled-skills/maggie-deployment/SKILL.md +48 -0
  27. package/bundled-skills/maggie-deployment/references/cloudflare.md +19 -0
  28. package/bundled-skills/maggie-deployment/references/vps.md +92 -0
  29. package/bundled-skills/maggie-design/SKILL.md +98 -3
  30. package/bundled-skills/maggie-design/references/app-ui-patterns.md +79 -0
  31. package/bundled-skills/maggie-feedback/SKILL.md +27 -2
  32. package/bundled-skills/maggie-marketplace/SKILL.md +12 -0
  33. package/bundled-skills/maggie-marketplace/references/directory-search.md +51 -0
  34. package/bundled-skills/maggie-marketplace/references/directory.mjs +112 -0
  35. package/bundled-skills/maggie-mobile-app/SKILL.md +65 -6
  36. package/bundled-skills/maggie-mobile-app/references/payments-and-push.md +78 -0
  37. package/bundled-skills/maggie-qa-workflow/SKILL.md +60 -0
  38. package/bundled-skills/maggie-seo-geo/SKILL.md +33 -0
  39. package/bundled-skills/maggie-service-booking/SKILL.md +28 -3
  40. package/bundled-skills/maggie-social-share/SKILL.md +56 -4
  41. package/bundled-skills/maggie-social-share/references/queue-and-connections.md +28 -0
  42. package/bundled-tools/clis/maggie_assistant_tools.py +84 -3
  43. package/bundled-tools/clis/maggie_booking.py +24 -12
  44. package/bundled-tools/clis/maggie_booking_connect_qa.py +136 -0
  45. package/bundled-tools/clis/maggie_booking_rules.py +476 -0
  46. package/bundled-tools/clis/maggie_bridge_host.py +77 -1
  47. package/bundled-tools/clis/maggie_dash.py +173 -3
  48. package/bundled-tools/clis/maggie_deployment_ops.py +524 -0
  49. package/bundled-tools/clis/maggie_design.py +15 -2
  50. package/bundled-tools/clis/maggie_design_bulk.py +290 -0
  51. package/bundled-tools/clis/maggie_design_lint.py +205 -3
  52. package/bundled-tools/clis/maggie_feedback.py +138 -28
  53. package/bundled-tools/clis/maggie_live_urls.py +138 -0
  54. package/bundled-tools/clis/maggie_localization.py +3 -0
  55. package/bundled-tools/clis/maggie_localization_keys.py +151 -0
  56. package/bundled-tools/clis/maggie_media_generate.py +174 -0
  57. package/bundled-tools/clis/maggie_mobile.py +219 -4
  58. package/bundled-tools/clis/maggie_product_release.py +49 -1
  59. package/bundled-tools/clis/maggie_qa_app_preflight.py +122 -4
  60. package/bundled-tools/clis/maggie_qa_fit.py +217 -0
  61. package/bundled-tools/clis/maggie_qa_workflow.py +5 -0
  62. package/bundled-tools/clis/maggie_schema_complexity.py +84 -0
  63. package/bundled-tools/clis/maggie_seo_routes.py +154 -0
  64. package/bundled-tools/clis/maggie_service_migration.py +272 -0
  65. package/bundled-tools/clis/maggie_social_queue.py +2 -0
  66. package/bundled-tools/clis/maggie_social_share.py +174 -7
  67. package/bundled-tools/integrations/maggie-api-pull.md +22 -0
  68. package/bundled-tools/runtime/assistant_approval.py +98 -0
  69. package/package.json +1 -1
  70. package/references/provider-adapter-contract.md +16 -3
  71. package/bundled-contracts/maggiedash/execution-board.json +0 -5463
  72. package/bundled-references/maggiedash-booking/CURRENT-STATE.md +0 -210
  73. package/bundled-references/maggiedash-booking/EXECUTION-BOARD.json +0 -5463
  74. package/bundled-references/maggiedash-booking/ORAWELLNESS-INTEGRATION-AUDIT.md +0 -270
  75. package/bundled-references/maggiedash-booking/PROGRESS.md +0 -279
  76. package/bundled-references/maggiedash-booking/README.md +0 -288
  77. package/bundled-references/maggiedash-booking/SKILLS-AND-CLI.md +0 -551
  78. package/bundled-references/maggiedash-booking/TASK-RUNBOOK.md +0 -107
  79. 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 [`../../bundled-references/maggiedash-booking/README.md`](../../bundled-references/maggiedash-booking/README.md),
25
- [`TASKS.md`](../../bundled-references/maggiedash-booking/TASKS.md), and the task's linked
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
- For implementation work, use the leaf board and runbook:
36
- [`EXECUTION-BOARD.json`](../../bundled-references/maggiedash-booking/EXECUTION-BOARD.json)
37
- and [`TASK-RUNBOOK.md`](../../bundled-references/maggiedash-booking/TASK-RUNBOOK.md). Do not
38
- mark a task passed until its implementation gate, docs update, and evidence
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 the public MaggieDash `0.2.8` distribution. It
119
- contains the released Astro `hostBootstrap` manifest and 47-route Booking
120
- source, including the tenant-scoped treatment package catalogue. A fresh public-source install, `maggie booking inspect`, and
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, ORA mirror,
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
- [`PROGRESS.md`](../../bundled-references/maggiedash-booking/PROGRESS.md).
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 in
36
- `.maggie/dash-install.json`. It never accepts or stores a GitHub token as a
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. Keep `css-cascade-check` for the rendered-browser proof.
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.