@wix/himalaya-cli 0.1141.0 → 0.1143.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/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/delivery-contract.d.ts +104 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/delivery.d.ts +33 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/index.d.ts +5 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +509 -3
- package/dist/cli.mjs +31398 -30604
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/designing-himi-screens.md +1 -1
- package/guides/getting-started.md +29 -11
- package/guides/himi-skill.md +29 -11
- package/guides/sdk-reference.json +132 -0
- package/guides/templates.md +33 -0
- package/guides/trim-a-template.md +13 -0
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +2 -0
- package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +2 -2
- package/templates/beauty-salon/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/branded-lite/dev/index.ts +3 -1
- package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +2 -2
- package/templates/branded-lite/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/branded-lite/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/community-club/dev/index.ts +2 -1
- package/templates/community-club/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/community-club/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/events-meetups/dev/index.ts +2 -1
- package/templates/events-meetups/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/events-meetups/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/gym-fitness/dev/index.ts +3 -1
- package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +2 -2
- package/templates/gym-fitness/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/gym-fitness/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/nonprofit-cause/dev/index.ts +2 -1
- package/templates/nonprofit-cause/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/nonprofit-cause/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/podcast-creator/dev/index.ts +2 -1
- package/templates/podcast-creator/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/podcast-creator/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/portfolio-creative/dev/index.ts +2 -1
- package/templates/portfolio-creative/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/portfolio-creative/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/restaurant/dev/index.ts +2 -1
- package/templates/restaurant/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/restaurant/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/retail-store/dev/index.ts +1 -0
- package/templates/retail-store/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/yoga-studio/dev/index.ts +3 -1
- package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +2 -2
- package/templates/yoga-studio/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/yoga-studio/tier5-src/himi-store-account/index.ts +7 -1
|
Binary file
|
|
@@ -281,7 +281,7 @@ Concrete patterns lifted from real usage:
|
|
|
281
281
|
- **Cell list** — `card([ vstack({ spacing: 0, alignment: "leading", children: [ …ListItem cells… ] }) ])`. `spacing: 0` because the cells own their own dividers/padding.
|
|
282
282
|
- **Overlay** — `zstack({ alignment: "center", children: [ backdropImage(cornerRadius 12), withModifiers(label, { padding, background: { token: "accentPrimary" }, cornerRadius: 10 }) ] })`.
|
|
283
283
|
- **Responsive** — `grid({ columns: 1, columnsMedium: 2, spacing: 12 })` for 1-up on phone / 2-up on tablet; `splitView({ primary, detail, primaryMaxWidth: 280 })` for master/detail that stacks on a phone; set `contentMaxWidth` in tokens so wide surfaces keep a readable column instead of running edge-to-edge.
|
|
284
|
-
- **Centered separator, not a flanked divider** — the classic "—— or ——" needs each `divider()` to take equal remaining width, and the HStack row-share only kicks in for controls with an intrinsic fill. **A bare `divider()` inside an HStack is a column separator, not a flanking line**: iOS, macOS, Android and
|
|
284
|
+
- **Centered separator, not a flanked divider** — the classic "—— or ——" needs each `divider()` to take equal remaining width, and the HStack row-share only kicks in for controls with an intrinsic fill. **A bare `divider()` inside an HStack is a column separator, not a flanking line**: iOS, macOS, Android, web and React Native all draw it as a thin vertical rule the height of the row that takes no share of the width, which is right for a stats row (`12 | 340 | 87`) and wrong for "—— or ——". For "—— or ——", use a **centered caption**: `hstack({ children: [spacer(), text("or continue with", "caption", { color: "textSecondary" }), spacer()] })`, optionally over a single full-width `divider()`.
|
|
285
285
|
|
|
286
286
|
> **Intrinsic-fill controls grab the row.** `Button` and `TextField` have an intrinsic FILL, so the next four idioms are about NOT letting that surprise you (they were the papercuts every off-repo app hit):
|
|
287
287
|
|
|
@@ -75,7 +75,11 @@ or `--template owner-lite` (owner CRUD/management: lists, details, forms). That
|
|
|
75
75
|
worker layer — trim it or mine it (`himi docs trim-a-template`): **trim** to the approved
|
|
76
76
|
`SPEC.md` when most of its screens fit, or **mine** it when few do — keep the
|
|
77
77
|
`tier5-src/_shared/` helpers and Wix worker chains and write your own screens. Do not delete
|
|
78
|
-
`tier5-src/` + `dev/` and start from blank.
|
|
78
|
+
`tier5-src/` + `dev/` and start from blank. Never read that file whole: `himi template screens`
|
|
79
|
+
lists its screens (size, workers, `onAppear`), `--show <id>` prints one entry, and
|
|
80
|
+
`--write <id> --from <file>` puts an edited entry back (refused if it breaks the file). Not sure
|
|
81
|
+
which template fits? `himi template recommend --text "<the business>"` (or `--book <site analyze
|
|
82
|
+
dir>` for a site's verticals) ranks the catalog with reasons. For a small **non-Wix** pattern, `himi init --template chat-bot --remote`. `himi template
|
|
79
83
|
list --remote` shows the full catalog (title/description/tags) — including templates published
|
|
80
84
|
to Wix — and `--remote` on `himi init` fetches one from there (the bundled copy is
|
|
81
85
|
the offline fallback). Publish any app of your own as a template with `himi template publish` —
|
|
@@ -102,7 +106,12 @@ done. Print one yourself with `himi template prompt <name>`.
|
|
|
102
106
|
**Consent preflight before a template preview.** `branded-lite` and `owner-lite` are Wix-integrated
|
|
103
107
|
full apps; their workers can request Wix data when they boot. Do not start their worker or web
|
|
104
108
|
host under a synthetic/local-only authorization unless you have first removed or mocked every such
|
|
105
|
-
startup path and verified the network log is empty of Wix/external requests.
|
|
109
|
+
startup path and verified the network log is empty of Wix/external requests. You do not have to
|
|
110
|
+
read the workers to know: `himi init --template` answers it in its envelope's `adopt` block
|
|
111
|
+
(`bootsOffline`, and `.himi/adopt.json` → `bootRequests` names each screen action and the Wix
|
|
112
|
+
modules or `fetch` it can reach; `himi template inspect` recomputes it after edits). It is a
|
|
113
|
+
static answer on the safe side — "may request" — so `bootsOffline: true` clears the preflight and
|
|
114
|
+
`false` means authorize, mock or remove those paths first. Prefer a blank,
|
|
106
115
|
local-only scaffold for that stage. A browser render that causes an unapproved request is a failed
|
|
107
116
|
consent gate, not a harmless preview.
|
|
108
117
|
|
|
@@ -113,10 +122,11 @@ web host and call it owner-private, and do not let a template worker boot there
|
|
|
113
122
|
network paths are authorized and preflighted.
|
|
114
123
|
|
|
115
124
|
**Adopt, don't just rename, a template.** The approved `SPEC.md` and `MOBILE-UX.md` are the
|
|
116
|
-
plan, and the plan wins over anything `himi init` seeds. `init`
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if either holds `init`'s boilerplate rather than the approved plan
|
|
125
|
+
plan, and the plan wins over anything `himi init` seeds. `init` keeps a `SPEC.md` /
|
|
126
|
+
`MOBILE-UX.md` already in the target folder — its envelope lists them under `kept` — and seeds
|
|
127
|
+
its skeleton only where none exists, so write the approved plan there before `init`. After
|
|
128
|
+
`init`, open both files: if either holds `init`'s boilerplate rather than the approved plan (an
|
|
129
|
+
older CLI, or a plan kept elsewhere), put the approved plan back in
|
|
120
130
|
its place — never the reverse — and finish it with the actual customer, data/auth assumptions,
|
|
121
131
|
placeholder copy, flow/state matrix, and compact/phone/expanded + device evidence before calling
|
|
122
132
|
the app ready. Keep scenario switches out of customer UI. A labelled preview fallback is acceptable
|
|
@@ -126,18 +136,26 @@ only when the host cannot perform the task and it cannot write customer data.
|
|
|
126
136
|
product. Right after `init`:
|
|
127
137
|
|
|
128
138
|
1. **Inventory what you inherited** before editing: its screens, local and remote data, auth,
|
|
129
|
-
capabilities, demo/fallback behaviour, and template-specific copy.
|
|
130
|
-
|
|
139
|
+
capabilities, demo/fallback behaviour, and template-specific copy. A current `himi init
|
|
140
|
+
--template` writes it for you as `.himi/adopt.json` (`himi template inspect` rewrites it) —
|
|
141
|
+
the list the next steps work through.
|
|
131
142
|
2. **Replace the template's identity**: its app name, addresses, sample content, and every
|
|
132
143
|
`YOUR_*` / `replace with` placeholder. A placeholder left in is shipped as copy —
|
|
133
|
-
`himi ux audit --strict` fails on one as `template-placeholder`.
|
|
144
|
+
`himi ux audit --strict` fails on one as `template-placeholder`. `adopt.json` lists every
|
|
145
|
+
placeholder and sample-copy hit; `himi template sweep --set "Old=>New"` replaces them inside
|
|
146
|
+
string literals only.
|
|
134
147
|
3. **Put each inherited risky flow in the acceptance matrix** — booking/cancellation, payments,
|
|
135
|
-
deletion, media, maps, entitlement — or remove it (`himi template trim`
|
|
136
|
-
|
|
148
|
+
deletion, media, maps, entitlement — or remove it (`himi template trim`; `--from-spec` keeps
|
|
149
|
+
exactly the screen ids in `SPEC.md`'s Screen catalogue — dry-run first and add every
|
|
150
|
+
`fromSpec.droppedPrimary` target, or a kept screen loses its own primary action while `validate`
|
|
151
|
+
stays green). A flow the plan does not mention is a
|
|
152
|
+
flow nobody approved. `.himi/adopt-matrix.md` proposes the `## Data access` rows from the
|
|
153
|
+
template's actions (writes, payments, destructive) — proposals for the owner, never approvals.
|
|
137
154
|
4. **Classify every control** as primary, secondary, destructive, or preview/fallback, and keep a
|
|
138
155
|
destructive or writing control only when the approved data-access matrix allows it.
|
|
139
156
|
5. **Prove each adopted flow, not just each screen**: with fixtures or `dev/net-mocks.json`, prove
|
|
140
157
|
typed input → submit → the persisted result with a typed journey in `himi-test.json`
|
|
158
|
+
(`.himi/himi-test.stub.json` has a skeleton per form: fill the values and expected text)
|
|
141
159
|
(`--deep` replays action chains but never types a field value), then prove the destination
|
|
142
160
|
and cancel/back recovery separately — a typed journey is screen-local and does not follow
|
|
143
161
|
navigation, so use a render of the destination or a device drive. A fresh-screen crawl alone
|
package/guides/himi-skill.md
CHANGED
|
@@ -104,7 +104,11 @@ or `--template owner-lite` (owner CRUD/management: lists, details, forms). That
|
|
|
104
104
|
worker layer — trim it or mine it (`himi docs trim-a-template`): **trim** to the approved
|
|
105
105
|
`SPEC.md` when most of its screens fit, or **mine** it when few do — keep the
|
|
106
106
|
`tier5-src/_shared/` helpers and Wix worker chains and write your own screens. Do not delete
|
|
107
|
-
`tier5-src/` + `dev/` and start from blank.
|
|
107
|
+
`tier5-src/` + `dev/` and start from blank. Never read that file whole: `himi template screens`
|
|
108
|
+
lists its screens (size, workers, `onAppear`), `--show <id>` prints one entry, and
|
|
109
|
+
`--write <id> --from <file>` puts an edited entry back (refused if it breaks the file). Not sure
|
|
110
|
+
which template fits? `himi template recommend --text "<the business>"` (or `--book <site analyze
|
|
111
|
+
dir>` for a site's verticals) ranks the catalog with reasons. For a small **non-Wix** pattern, `himi init --template chat-bot --remote`. `himi template
|
|
108
112
|
list --remote` shows the full catalog (title/description/tags) — including templates published
|
|
109
113
|
to Wix — and `--remote` on `himi init` fetches one from there (the bundled copy is
|
|
110
114
|
the offline fallback). Publish any app of your own as a template with `himi template publish` —
|
|
@@ -131,7 +135,12 @@ done. Print one yourself with `himi template prompt <name>`.
|
|
|
131
135
|
**Consent preflight before a template preview.** `branded-lite` and `owner-lite` are Wix-integrated
|
|
132
136
|
full apps; their workers can request Wix data when they boot. Do not start their worker or web
|
|
133
137
|
host under a synthetic/local-only authorization unless you have first removed or mocked every such
|
|
134
|
-
startup path and verified the network log is empty of Wix/external requests.
|
|
138
|
+
startup path and verified the network log is empty of Wix/external requests. You do not have to
|
|
139
|
+
read the workers to know: `himi init --template` answers it in its envelope's `adopt` block
|
|
140
|
+
(`bootsOffline`, and `.himi/adopt.json` → `bootRequests` names each screen action and the Wix
|
|
141
|
+
modules or `fetch` it can reach; `himi template inspect` recomputes it after edits). It is a
|
|
142
|
+
static answer on the safe side — "may request" — so `bootsOffline: true` clears the preflight and
|
|
143
|
+
`false` means authorize, mock or remove those paths first. Prefer a blank,
|
|
135
144
|
local-only scaffold for that stage. A browser render that causes an unapproved request is a failed
|
|
136
145
|
consent gate, not a harmless preview.
|
|
137
146
|
|
|
@@ -142,10 +151,11 @@ web host and call it owner-private, and do not let a template worker boot there
|
|
|
142
151
|
network paths are authorized and preflighted.
|
|
143
152
|
|
|
144
153
|
**Adopt, don't just rename, a template.** The approved `SPEC.md` and `MOBILE-UX.md` are the
|
|
145
|
-
plan, and the plan wins over anything `himi init` seeds. `init`
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if either holds `init`'s boilerplate rather than the approved plan
|
|
154
|
+
plan, and the plan wins over anything `himi init` seeds. `init` keeps a `SPEC.md` /
|
|
155
|
+
`MOBILE-UX.md` already in the target folder — its envelope lists them under `kept` — and seeds
|
|
156
|
+
its skeleton only where none exists, so write the approved plan there before `init`. After
|
|
157
|
+
`init`, open both files: if either holds `init`'s boilerplate rather than the approved plan (an
|
|
158
|
+
older CLI, or a plan kept elsewhere), put the approved plan back in
|
|
149
159
|
its place — never the reverse — and finish it with the actual customer, data/auth assumptions,
|
|
150
160
|
placeholder copy, flow/state matrix, and compact/phone/expanded + device evidence before calling
|
|
151
161
|
the app ready. Keep scenario switches out of customer UI. A labelled preview fallback is acceptable
|
|
@@ -155,18 +165,26 @@ only when the host cannot perform the task and it cannot write customer data.
|
|
|
155
165
|
product. Right after `init`:
|
|
156
166
|
|
|
157
167
|
1. **Inventory what you inherited** before editing: its screens, local and remote data, auth,
|
|
158
|
-
capabilities, demo/fallback behaviour, and template-specific copy.
|
|
159
|
-
|
|
168
|
+
capabilities, demo/fallback behaviour, and template-specific copy. A current `himi init
|
|
169
|
+
--template` writes it for you as `.himi/adopt.json` (`himi template inspect` rewrites it) —
|
|
170
|
+
the list the next steps work through.
|
|
160
171
|
2. **Replace the template's identity**: its app name, addresses, sample content, and every
|
|
161
172
|
`YOUR_*` / `replace with` placeholder. A placeholder left in is shipped as copy —
|
|
162
|
-
`himi ux audit --strict` fails on one as `template-placeholder`.
|
|
173
|
+
`himi ux audit --strict` fails on one as `template-placeholder`. `adopt.json` lists every
|
|
174
|
+
placeholder and sample-copy hit; `himi template sweep --set "Old=>New"` replaces them inside
|
|
175
|
+
string literals only.
|
|
163
176
|
3. **Put each inherited risky flow in the acceptance matrix** — booking/cancellation, payments,
|
|
164
|
-
deletion, media, maps, entitlement — or remove it (`himi template trim`
|
|
165
|
-
|
|
177
|
+
deletion, media, maps, entitlement — or remove it (`himi template trim`; `--from-spec` keeps
|
|
178
|
+
exactly the screen ids in `SPEC.md`'s Screen catalogue — dry-run first and add every
|
|
179
|
+
`fromSpec.droppedPrimary` target, or a kept screen loses its own primary action while `validate`
|
|
180
|
+
stays green). A flow the plan does not mention is a
|
|
181
|
+
flow nobody approved. `.himi/adopt-matrix.md` proposes the `## Data access` rows from the
|
|
182
|
+
template's actions (writes, payments, destructive) — proposals for the owner, never approvals.
|
|
166
183
|
4. **Classify every control** as primary, secondary, destructive, or preview/fallback, and keep a
|
|
167
184
|
destructive or writing control only when the approved data-access matrix allows it.
|
|
168
185
|
5. **Prove each adopted flow, not just each screen**: with fixtures or `dev/net-mocks.json`, prove
|
|
169
186
|
typed input → submit → the persisted result with a typed journey in `himi-test.json`
|
|
187
|
+
(`.himi/himi-test.stub.json` has a skeleton per form: fill the values and expected text)
|
|
170
188
|
(`--deep` replays action chains but never types a field value), then prove the destination
|
|
171
189
|
and cancel/back recovery separately — a typed journey is screen-local and does not follow
|
|
172
190
|
navigation, so use a render of the destination or a device drive. A fresh-screen crawl alone
|
|
@@ -1331,6 +1331,132 @@
|
|
|
1331
1331
|
"source": "core/sdk/iap/src/index.ts",
|
|
1332
1332
|
"description": "Tier-5 typed binding to the platform's in-app-purchase capability.",
|
|
1333
1333
|
"api": [
|
|
1334
|
+
{
|
|
1335
|
+
"name": "SessionSnapshot",
|
|
1336
|
+
"kind": "interface",
|
|
1337
|
+
"jsdoc": "/** Trusted host session snapshot. Not a credential: the backend adapter resolves real\n * credentials from its own authenticated transport, bound to this `generation`. */",
|
|
1338
|
+
"decl": "export interface SessionSnapshot {\n /** Changes on every sign-in, sign-out, member or site change. */\n generation: number;\n kind: \"member\" | \"visitor\" | \"owner\" | \"signedOut\";\n}"
|
|
1339
|
+
},
|
|
1340
|
+
{
|
|
1341
|
+
"name": "SessionProvider",
|
|
1342
|
+
"kind": "interface",
|
|
1343
|
+
"jsdoc": "/** Supplied by the host, never by worker UI state. */",
|
|
1344
|
+
"decl": "export interface SessionProvider {\n current(): SessionSnapshot;\n /** Called after `current()` changes; returns an unsubscribe function. */\n subscribe(listener: () => void): () => void;\n}"
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
"name": "PurchaseContext",
|
|
1348
|
+
"kind": "interface",
|
|
1349
|
+
"jsdoc": "/** A catalog item resolved by the backend for the captured member. */",
|
|
1350
|
+
"decl": "export interface PurchaseContext {\n productId: string;\n /** Canonical UUID the backend associates with this site/member. */\n appAccountToken: string;\n /** Opaque site/member scope the acknowledgement must echo. */\n scopeKey: string;\n}"
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
"name": "MemberContext",
|
|
1354
|
+
"kind": "interface",
|
|
1355
|
+
"jsdoc": "/** The current member's association, used to recover purchases without a catalog item. */",
|
|
1356
|
+
"decl": "export interface MemberContext {\n appAccountToken: string;\n scopeKey: string;\n}"
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
"name": "ContextResult",
|
|
1360
|
+
"kind": "type",
|
|
1361
|
+
"jsdoc": "",
|
|
1362
|
+
"decl": "export type ContextResult<T> =\n | { status: \"ok\"; context: T }\n | { status: \"authRequired\" | \"rejected\" }\n | { status: \"retryable\"; retryAfterMs?: number };"
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
"name": "ProcessingAcknowledgement",
|
|
1366
|
+
"kind": "interface",
|
|
1367
|
+
"jsdoc": "/** Bound to one transaction and scope. Means verification AND durable access persistence\n * completed; an HTTP 2xx, a receipt or a job id is not an acknowledgement. */",
|
|
1368
|
+
"decl": "export interface ProcessingAcknowledgement {\n transactionId: string;\n productId: string;\n environment: AppleTransaction[\"environment\"];\n scopeKey: string;\n}"
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
"name": "SubmitProofResult",
|
|
1372
|
+
"kind": "type",
|
|
1373
|
+
"jsdoc": "",
|
|
1374
|
+
"decl": "export type SubmitProofResult =\n | { status: \"processed\" | \"alreadyProcessed\"; acknowledgement: ProcessingAcknowledgement }\n | { status: \"retryable\"; retryAfterMs?: number }\n | { status: \"authRequired\" | \"ownershipConflict\" | \"rejected\" };"
|
|
1375
|
+
},
|
|
1376
|
+
{
|
|
1377
|
+
"name": "DeliveryBackend",
|
|
1378
|
+
"kind": "interface",
|
|
1379
|
+
"jsdoc": "/** PROPOSED backend agreement. Every call carries the session captured when the work\n * started, never whichever session is current when the request is sent. Responses are\n * decoded strictly; anything unrecognised is treated as invalid, never as success. */",
|
|
1380
|
+
"decl": "export interface DeliveryBackend {\n getPurchaseContext(session: SessionSnapshot, catalogRef: string): Promise<ContextResult<PurchaseContext>>;\n getMemberContext(session: SessionSnapshot): Promise<ContextResult<MemberContext>>;\n /** Must be idempotent per store/environment/transaction id; delivery is at-least-once. */\n submitProof(session: SessionSnapshot, transaction: AppleTransaction): Promise<SubmitProofResult>;\n}"
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
"name": "TransactionStore",
|
|
1384
|
+
"kind": "interface",
|
|
1385
|
+
"jsdoc": "/** The three managed verbs; `appleManagedStore` in index.ts is the production binding. */",
|
|
1386
|
+
"decl": "export interface TransactionStore {\n purchaseForVerification(input: { productId: string; appAccountToken: string }): Promise<VerificationPurchaseResult>;\n getUnfinishedTransactions(): Promise<UnfinishedTransactionsResult>;\n finishTransaction(input: { transactionId: string }): Promise<FinishTransactionResult>;\n}"
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
"name": "Clock",
|
|
1390
|
+
"kind": "interface",
|
|
1391
|
+
"jsdoc": "",
|
|
1392
|
+
"decl": "export interface Clock {\n /** Epoch milliseconds. */\n now(): number;\n}"
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
"name": "DeliveryPhase",
|
|
1396
|
+
"kind": "type",
|
|
1397
|
+
"jsdoc": "",
|
|
1398
|
+
"decl": "export type DeliveryPhase = \"idle\" | \"purchasing\" | \"awaitingApproval\" | \"cancelled\" | \"signInRequired\" | \"unavailable\";"
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
"name": "DeliveryErrorCode",
|
|
1402
|
+
"kind": "type",
|
|
1403
|
+
"jsdoc": "/** `sessionChanged`: a purchase was requested under an earlier session and was not carried out. */",
|
|
1404
|
+
"decl": "export type DeliveryErrorCode = TransactionErrorCode | \"contextUnavailable\" | \"contextRejected\" | \"proofRejected\"\n | \"sessionChanged\";"
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
"name": "AccessStatus",
|
|
1408
|
+
"kind": "type",
|
|
1409
|
+
"jsdoc": "",
|
|
1410
|
+
"decl": "export type AccessStatus = \"pending\" | \"confirming\" | \"waitingToRetry\" | \"signInRequired\"\n | \"ownershipConflict\" | \"rejected\" | \"confirmed\";"
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
"name": "CompletionStatus",
|
|
1414
|
+
"kind": "type",
|
|
1415
|
+
"jsdoc": "",
|
|
1416
|
+
"decl": "export type CompletionStatus = \"pending\" | \"finished\" | \"notFound\" | \"retryRequired\";"
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
"name": "DeliveryItemState",
|
|
1420
|
+
"kind": "interface",
|
|
1421
|
+
"jsdoc": "/** Safe to render and persist: no proof, account token, transaction or member id. */",
|
|
1422
|
+
"decl": "export interface DeliveryItemState {\n productId: string;\n environment: AppleTransaction[\"environment\"];\n /** Present in the last native unfinished enumeration; null before one succeeded. */\n held: boolean | null;\n /** `confirmed` only from a matching acknowledgement for the current session. */\n access: AccessStatus;\n completion: CompletionStatus;\n attempts: number;\n nextAttemptAt: number | null;\n errorCode: DeliveryErrorCode | null;\n}"
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
"name": "DeliveryState",
|
|
1426
|
+
"kind": "interface",
|
|
1427
|
+
"jsdoc": "",
|
|
1428
|
+
"decl": "export interface DeliveryState {\n phase: DeliveryPhase;\n errorCode: DeliveryErrorCode | null;\n items: DeliveryItemState[];\n /** From the last successful enumeration; null when unknown or it failed. */\n unfinishedCount: number | null;\n /** Earliest `nextAttemptAt`; hosts schedule `tick()` for it (e.g. a worker `_poll`). */\n nextWakeAt: number | null;\n}"
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
"name": "PurchaseCoordinatorDependencies",
|
|
1432
|
+
"kind": "interface",
|
|
1433
|
+
"jsdoc": "",
|
|
1434
|
+
"decl": "export interface PurchaseCoordinatorDependencies {\n store: TransactionStore;\n backend: DeliveryBackend;\n session: SessionProvider;\n clock: Clock;\n /** Extra milliseconds added to a backoff step; inject for deterministic tests. */\n jitter?: (baseDelayMs: number) => number;\n /** Receives a fresh safe snapshot after every change. */\n onState?: (state: DeliveryState) => void;\n}"
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
"name": "PurchaseCoordinator",
|
|
1438
|
+
"kind": "interface",
|
|
1439
|
+
"jsdoc": "",
|
|
1440
|
+
"decl": "export interface PurchaseCoordinator {\n /** Reconciles unfinished purchases now and again after every later session change. */\n start(): Promise<DeliveryState>;\n /** Buys a catalog item; the backend resolves its product id and account token.\n * The same member's purchase of the same item already in progress is shared, not repeated. */\n purchase(catalogRef: string): Promise<DeliveryState>;\n /** Re-enumerates unfinished purchases and delivers eligible ones (e.g. on resume). */\n reconcile(): Promise<DeliveryState>;\n /** Unblocks every waiting or blocked item with a fresh attempt budget, then reconciles. */\n retry(): Promise<DeliveryState>;\n /** Runs whatever retries are due at `clock.now()`. */\n tick(): Promise<DeliveryState>;\n getState(): DeliveryState;\n /** Drops the session subscription and suppresses every later callback. */\n dispose(): void;\n}"
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
"name": "MAX_AUTOMATIC_ATTEMPTS",
|
|
1444
|
+
"kind": "const",
|
|
1445
|
+
"jsdoc": "/** Automatic attempts per item per reconciliation run; the item is never discarded after. */",
|
|
1446
|
+
"decl": "export const MAX_AUTOMATIC_ATTEMPTS = 5;"
|
|
1447
|
+
},
|
|
1448
|
+
{
|
|
1449
|
+
"name": "MAX_RETRY_DELAY_MS",
|
|
1450
|
+
"kind": "const",
|
|
1451
|
+
"jsdoc": "",
|
|
1452
|
+
"decl": "export const MAX_RETRY_DELAY_MS = 60_000;"
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
"name": "createPurchaseCoordinator",
|
|
1456
|
+
"kind": "function",
|
|
1457
|
+
"jsdoc": "/** Creates a coordinator owned by the host/service lifecycle, not by a screen or render.\n * Closures, not classes: Tier-5 bundles must also evaluate on Hermes, which has no `class`. */",
|
|
1458
|
+
"decl": "export function createPurchaseCoordinator(deps: PurchaseCoordinatorDependencies): PurchaseCoordinator;"
|
|
1459
|
+
},
|
|
1334
1460
|
{
|
|
1335
1461
|
"name": "ProductType",
|
|
1336
1462
|
"kind": "type",
|
|
@@ -1462,6 +1588,12 @@
|
|
|
1462
1588
|
"kind": "function",
|
|
1463
1589
|
"jsdoc": "/** Trusted callers must first obtain durable backend processing acknowledgement. */",
|
|
1464
1590
|
"decl": "export async function finishTransaction(input: { transactionId: string }): Promise<FinishTransactionResult>;"
|
|
1591
|
+
},
|
|
1592
|
+
{
|
|
1593
|
+
"name": "appleManagedStore",
|
|
1594
|
+
"kind": "const",
|
|
1595
|
+
"jsdoc": "/** The managed native verbs as the delivery coordinator's store. */",
|
|
1596
|
+
"decl": "export const appleManagedStore: TransactionStore = { purchaseForVerification, getUnfinishedTransactions, finishTransaction };"
|
|
1465
1597
|
}
|
|
1466
1598
|
]
|
|
1467
1599
|
},
|
package/guides/templates.md
CHANGED
|
@@ -93,6 +93,39 @@ as a raw descriptor (megabytes for the full templates), so replacing it is often
|
|
|
93
93
|
editing it. Both paths, and how `dev/index.ts`, `tier5-src/`, and `navigation.linkableScreens`
|
|
94
94
|
relate: `himi docs trim-a-template`.
|
|
95
95
|
|
|
96
|
+
## Adopt — what the CLI works out for you
|
|
97
|
+
|
|
98
|
+
Starting from a template is a fork of a large app, and most of what an agent needs to know about it
|
|
99
|
+
is mechanical. `himi init --template` answers it in the envelope's `adopt` block and writes the
|
|
100
|
+
details beside the app:
|
|
101
|
+
|
|
102
|
+
| File | What it holds |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `.himi/adopt.json` | every screen (size, workers, `onAppear`), every worker's Wix modules / `wixApi` / `fetch` reach, navigation, **`bootRequests`** + **`bootsOffline`**, placeholder and sample-copy hits |
|
|
105
|
+
| `.himi/adopt-matrix.md` | proposed `## Data access` rows from the template's actions — writes, payments, destructive — in the exact table `himi test` reads. Proposals for the owner, never approvals |
|
|
106
|
+
| `.himi/himi-test.stub.json` | a typed-journey skeleton per form (every field, then its submit) with `<FILL>` slots |
|
|
107
|
+
|
|
108
|
+
`bootsOffline` is the consent preflight: it is `false` when any screen's `onAppear` dispatches into
|
|
109
|
+
a worker that can reach Wix or the network, and `bootRequests` names each one. It is static and errs
|
|
110
|
+
on the safe side ("may request"). The commands behind it:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
himi template recommend --text "a salon where clients book and buy products" # rank the catalog (or --verticals / --book)
|
|
114
|
+
himi template inspect # recompute the inventory after edits (--dry-run writes nothing)
|
|
115
|
+
himi template screens # list dev/index.ts screens without reading the file
|
|
116
|
+
himi template screens --show himi_saved > saved.txt # ONE screen's entry
|
|
117
|
+
himi template screens --write himi_saved --from saved.txt # put it back — re-parsed, refused if it breaks the file
|
|
118
|
+
himi template sweep --set "Sample content=>Our latest" --dry-run # copy inside string literals only
|
|
119
|
+
himi template trim --from-spec --links drop --dry-run # keep SPEC.md's Screen catalogue ids — read fromSpec.droppedPrimary first
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`sweep` works on the TypeScript AST, so comments and code are never candidates — only string
|
|
123
|
+
literals, and of those only prose (has whitespace), a template placeholder (`YOUR_*`), or the value
|
|
124
|
+
of a copy key (`title`, `text`, `label`, `placeholder`, …; `name` only in `config.ts`, because in a
|
|
125
|
+
descriptor it is an Icon id). A one-word literal elsewhere — a service name, a component type, an
|
|
126
|
+
action id, a URL — is wiring, and is never rewritten. Every changed file is syntax-checked before
|
|
127
|
+
anything is written.
|
|
128
|
+
|
|
96
129
|
**Point a `branded-lite` (or vertical overlay) scaffold at your site.** Set
|
|
97
130
|
`WIX_CLIENT_ID` (Headless OAuth client on that site) and `WIX_SITE_URL` before build /
|
|
98
131
|
preview — the `clientId` **is** the site binding (no separate MSID field). Placeholders
|
|
@@ -48,6 +48,19 @@ himi template trim --keep himi_shop,himi_product,himi_cart,himi_account --dry-ru
|
|
|
48
48
|
himi template trim --keep himi_shop,himi_product,himi_cart,himi_account --links drop # do it
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
**From the plan:** `himi template trim --from-spec [SPEC.md] --links drop` takes `--keep` from the
|
|
52
|
+
first column of `SPEC.md`'s `## Screen catalogue` (backticked screen ids; prose rows are ignored).
|
|
53
|
+
Its report adds `fromSpec.specOnly` — the plan screens the template does not have, which you author
|
|
54
|
+
(the "mine" part) — and `fromSpec.droppedPrimary`: each kept screen whose own primary action (a list
|
|
55
|
+
row's tap, a CTA) opened a screen the plan left out. `--links drop` removes those taps and `validate`
|
|
56
|
+
stays green, so a Services list that listed its services but could no longer book one passed every
|
|
57
|
+
check. Dry-run first, add each `droppedPrimary` target to the catalogue (or confirm the plan drops
|
|
58
|
+
that flow), then trim for real. A tap whose target a WORKER computes is invisible to trim; `himi test`
|
|
59
|
+
reports it as `nav-target-missing`. A trim that keeps its tree (a success, or `--force`) also
|
|
60
|
+
refreshes `.himi/adopt.json`; it leaves an existing `adopt-matrix.md` / `himi-test.stub.json` alone,
|
|
61
|
+
since those are the files you fill in (`himi template inspect` regenerates all three). `himi template screens`
|
|
62
|
+
lists the ids to put in that column.
|
|
63
|
+
|
|
51
64
|
Run it in (or point `--content` at) the app `himi init --template` scaffolded — not the
|
|
52
65
|
template itself, which it refuses. `--keep` takes `SCREENS` ids and templated route ids (the
|
|
53
66
|
part before the brackets: `himi_product` for `himi_product[_{id}]`). The home screen is always
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1143.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
|
|
6
6
|
"keywords": [
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"artifactId": "himalaya-cli"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
-
"falconPackageHash": "
|
|
70
|
+
"falconPackageHash": "7d92bfb40f295121fe145451b71e6d7d5f3bbba165b18f60bc10d4eb"
|
|
71
71
|
}
|
|
@@ -2493,6 +2493,7 @@ const SCREENS = runtimeSetting({
|
|
|
2493
2493
|
"type": "TextField",
|
|
2494
2494
|
"props": {
|
|
2495
2495
|
"placeholder": "Password",
|
|
2496
|
+
"valuePath": "$service:himiStoreAccount.password",
|
|
2496
2497
|
"secure": true,
|
|
2497
2498
|
"autoCapitalization": "none",
|
|
2498
2499
|
"onChange": {
|
|
@@ -27653,6 +27654,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
27653
27654
|
"type": "TextField",
|
|
27654
27655
|
"props": {
|
|
27655
27656
|
"placeholder": "Password",
|
|
27657
|
+
"valuePath": "$service:himiBookingTime.password",
|
|
27656
27658
|
"secure": true,
|
|
27657
27659
|
"autoCapitalization": "none",
|
|
27658
27660
|
"onChange": {
|
|
@@ -603,7 +603,7 @@ const svc = defineService<S, {
|
|
|
603
603
|
message: copyParam("time.signedInReady", "Signed in. You can book this time now."),
|
|
604
604
|
});
|
|
605
605
|
} catch {
|
|
606
|
-
return derive({ ...s, message: copyParam("time.signInFailed", "Sign in failed. Try again.") });
|
|
606
|
+
return derive({ ...s, password: "", message: copyParam("time.signInFailed", "Sign in failed. Try again.") });
|
|
607
607
|
}
|
|
608
608
|
},
|
|
609
609
|
register: async (s) => {
|
|
@@ -626,7 +626,7 @@ const svc = defineService<S, {
|
|
|
626
626
|
message: copyParam("time.registerReady", "Account created. You can book this time now."),
|
|
627
627
|
});
|
|
628
628
|
} catch {
|
|
629
|
-
return derive({ ...s, message: copyParam("time.registerFailed", "Account creation failed. Try another email.") });
|
|
629
|
+
return derive({ ...s, password: "", message: copyParam("time.registerFailed", "Account creation failed. Try another email.") });
|
|
630
630
|
}
|
|
631
631
|
},
|
|
632
632
|
bookSelected: async (s) => {
|
|
@@ -77,7 +77,10 @@ async function authenticate(s: S, submit: () => Promise<unknown>, notMemberMessa
|
|
|
77
77
|
if (!next.isMember) return derive({ ...next, message: notMemberMessage });
|
|
78
78
|
return resumeAfterAuth(derive({ ...next, message: "" }));
|
|
79
79
|
} catch {
|
|
80
|
-
|
|
80
|
+
// Clear the password: service state is snapshotted to disk (the worker-state
|
|
81
|
+
// store), so a failed attempt must not leave it there in cleartext. The Password
|
|
82
|
+
// field binds `valuePath` to it, so the field empties too and a retry re-types.
|
|
83
|
+
return derive({ ...s, password: "", message: failedMessage });
|
|
81
84
|
} finally {
|
|
82
85
|
authInFlight = false;
|
|
83
86
|
}
|
|
@@ -168,6 +171,9 @@ interface S {
|
|
|
168
171
|
noReturnPath: boolean;
|
|
169
172
|
returnCta: string;
|
|
170
173
|
email: string;
|
|
174
|
+
/** Held only from typing to the submit. Service state is persisted, so every
|
|
175
|
+
* submitted sign-in/register clears it (success, not-a-member, and failure), as
|
|
176
|
+
* do recovery and sign-out. Sign-in <-> register keeps it: the field stays up. */
|
|
171
177
|
password: string;
|
|
172
178
|
firstName: string;
|
|
173
179
|
lastName: string;
|
|
@@ -3080,6 +3080,7 @@ const SCREENS = runtimeSetting({
|
|
|
3080
3080
|
"type": "TextField",
|
|
3081
3081
|
"props": {
|
|
3082
3082
|
"placeholder": "Password",
|
|
3083
|
+
"valuePath": "$service:himiStoreAccount.password",
|
|
3083
3084
|
"secure": true,
|
|
3084
3085
|
"autoCapitalization": "none",
|
|
3085
3086
|
"onChange": {
|
|
@@ -82309,6 +82310,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
82309
82310
|
"type": "TextField",
|
|
82310
82311
|
"props": {
|
|
82311
82312
|
"placeholder": "Password",
|
|
82313
|
+
"valuePath": "$service:himiBookingTime.password",
|
|
82312
82314
|
"secure": true,
|
|
82313
82315
|
"autoCapitalization": "none",
|
|
82314
82316
|
"onChange": {
|
|
@@ -85183,7 +85185,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
85183
85185
|
"type": "TextField",
|
|
85184
85186
|
"props": {
|
|
85185
85187
|
"placeholder": "Password",
|
|
85186
|
-
"
|
|
85188
|
+
"valuePath": "$service:himiEventsRsvp.password",
|
|
85187
85189
|
"secure": true,
|
|
85188
85190
|
"autoCapitalization": "none",
|
|
85189
85191
|
"onChange": {
|
|
@@ -603,7 +603,7 @@ const svc = defineService<S, {
|
|
|
603
603
|
message: copyParam("time.signedInReady", "Signed in. You can book this time now."),
|
|
604
604
|
});
|
|
605
605
|
} catch {
|
|
606
|
-
return derive({ ...s, message: copyParam("time.signInFailed", "Sign in failed. Try again.") });
|
|
606
|
+
return derive({ ...s, password: "", message: copyParam("time.signInFailed", "Sign in failed. Try again.") });
|
|
607
607
|
}
|
|
608
608
|
},
|
|
609
609
|
register: async (s) => {
|
|
@@ -626,7 +626,7 @@ const svc = defineService<S, {
|
|
|
626
626
|
message: copyParam("time.registerReady", "Account created. You can book this time now."),
|
|
627
627
|
});
|
|
628
628
|
} catch {
|
|
629
|
-
return derive({ ...s, message: copyParam("time.registerFailed", "Account creation failed. Try another email.") });
|
|
629
|
+
return derive({ ...s, password: "", message: copyParam("time.registerFailed", "Account creation failed. Try another email.") });
|
|
630
630
|
}
|
|
631
631
|
},
|
|
632
632
|
bookSelected: async (s) => {
|
|
@@ -179,9 +179,9 @@ const svc = defineService<S, {
|
|
|
179
179
|
try {
|
|
180
180
|
await dispatch("himi.wix.signInDirect", { email: s.email, password: s.password });
|
|
181
181
|
const p = await probeMember({ ...s, isMember: true, identityLine: s.email });
|
|
182
|
-
return derive({ ...p, message: p.isMember ? copyParam("rsvp.signedInReady", "Signed in. You can RSVP now.") : copyParam("rsvp.signInCheckDetails", "Sign in failed. Check your details.") });
|
|
182
|
+
return derive({ ...p, password: "", message: p.isMember ? copyParam("rsvp.signedInReady", "Signed in. You can RSVP now.") : copyParam("rsvp.signInCheckDetails", "Sign in failed. Check your details.") });
|
|
183
183
|
} catch {
|
|
184
|
-
return derive({ ...s, message: copyParam("rsvp.signInFailed", "Sign in failed. Try again.") });
|
|
184
|
+
return derive({ ...s, password: "", message: copyParam("rsvp.signInFailed", "Sign in failed. Try again.") });
|
|
185
185
|
}
|
|
186
186
|
},
|
|
187
187
|
rsvp: async (s) => {
|
|
@@ -77,7 +77,10 @@ async function authenticate(s: S, submit: () => Promise<unknown>, notMemberMessa
|
|
|
77
77
|
if (!next.isMember) return derive({ ...next, message: notMemberMessage });
|
|
78
78
|
return resumeAfterAuth(derive({ ...next, message: "" }));
|
|
79
79
|
} catch {
|
|
80
|
-
|
|
80
|
+
// Clear the password: service state is snapshotted to disk (the worker-state
|
|
81
|
+
// store), so a failed attempt must not leave it there in cleartext. The Password
|
|
82
|
+
// field binds `valuePath` to it, so the field empties too and a retry re-types.
|
|
83
|
+
return derive({ ...s, password: "", message: failedMessage });
|
|
81
84
|
} finally {
|
|
82
85
|
authInFlight = false;
|
|
83
86
|
}
|
|
@@ -168,6 +171,9 @@ interface S {
|
|
|
168
171
|
noReturnPath: boolean;
|
|
169
172
|
returnCta: string;
|
|
170
173
|
email: string;
|
|
174
|
+
/** Held only from typing to the submit. Service state is persisted, so every
|
|
175
|
+
* submitted sign-in/register clears it (success, not-a-member, and failure), as
|
|
176
|
+
* do recovery and sign-out. Sign-in <-> register keeps it: the field stays up. */
|
|
171
177
|
password: string;
|
|
172
178
|
firstName: string;
|
|
173
179
|
lastName: string;
|
|
@@ -2115,6 +2115,7 @@ const SCREENS = runtimeSetting({
|
|
|
2115
2115
|
"type": "TextField",
|
|
2116
2116
|
"props": {
|
|
2117
2117
|
"placeholder": "Password",
|
|
2118
|
+
"valuePath": "$service:himiStoreAccount.password",
|
|
2118
2119
|
"secure": true,
|
|
2119
2120
|
"autoCapitalization": "none",
|
|
2120
2121
|
"onChange": {
|
|
@@ -18328,7 +18329,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
18328
18329
|
"type": "TextField",
|
|
18329
18330
|
"props": {
|
|
18330
18331
|
"placeholder": "Password",
|
|
18331
|
-
"
|
|
18332
|
+
"valuePath": "$service:himiEventsRsvp.password",
|
|
18332
18333
|
"secure": true,
|
|
18333
18334
|
"autoCapitalization": "none",
|
|
18334
18335
|
"onChange": {
|
|
@@ -179,9 +179,9 @@ const svc = defineService<S, {
|
|
|
179
179
|
try {
|
|
180
180
|
await dispatch("himi.wix.signInDirect", { email: s.email, password: s.password });
|
|
181
181
|
const p = await probeMember({ ...s, isMember: true, identityLine: s.email });
|
|
182
|
-
return derive({ ...p, message: p.isMember ? copyParam("rsvp.signedInReady", "Signed in. You can RSVP now.") : copyParam("rsvp.signInCheckDetails", "Sign in failed. Check your details.") });
|
|
182
|
+
return derive({ ...p, password: "", message: p.isMember ? copyParam("rsvp.signedInReady", "Signed in. You can RSVP now.") : copyParam("rsvp.signInCheckDetails", "Sign in failed. Check your details.") });
|
|
183
183
|
} catch {
|
|
184
|
-
return derive({ ...s, message: copyParam("rsvp.signInFailed", "Sign in failed. Try again.") });
|
|
184
|
+
return derive({ ...s, password: "", message: copyParam("rsvp.signInFailed", "Sign in failed. Try again.") });
|
|
185
185
|
}
|
|
186
186
|
},
|
|
187
187
|
rsvp: async (s) => {
|
|
@@ -77,7 +77,10 @@ async function authenticate(s: S, submit: () => Promise<unknown>, notMemberMessa
|
|
|
77
77
|
if (!next.isMember) return derive({ ...next, message: notMemberMessage });
|
|
78
78
|
return resumeAfterAuth(derive({ ...next, message: "" }));
|
|
79
79
|
} catch {
|
|
80
|
-
|
|
80
|
+
// Clear the password: service state is snapshotted to disk (the worker-state
|
|
81
|
+
// store), so a failed attempt must not leave it there in cleartext. The Password
|
|
82
|
+
// field binds `valuePath` to it, so the field empties too and a retry re-types.
|
|
83
|
+
return derive({ ...s, password: "", message: failedMessage });
|
|
81
84
|
} finally {
|
|
82
85
|
authInFlight = false;
|
|
83
86
|
}
|
|
@@ -168,6 +171,9 @@ interface S {
|
|
|
168
171
|
noReturnPath: boolean;
|
|
169
172
|
returnCta: string;
|
|
170
173
|
email: string;
|
|
174
|
+
/** Held only from typing to the submit. Service state is persisted, so every
|
|
175
|
+
* submitted sign-in/register clears it (success, not-a-member, and failure), as
|
|
176
|
+
* do recovery and sign-out. Sign-in <-> register keeps it: the field stays up. */
|
|
171
177
|
password: string;
|
|
172
178
|
firstName: string;
|
|
173
179
|
lastName: string;
|
|
@@ -2299,6 +2299,7 @@ const SCREENS = runtimeSetting({
|
|
|
2299
2299
|
"type": "TextField",
|
|
2300
2300
|
"props": {
|
|
2301
2301
|
"placeholder": "Password",
|
|
2302
|
+
"valuePath": "$service:himiStoreAccount.password",
|
|
2302
2303
|
"secure": true,
|
|
2303
2304
|
"autoCapitalization": "none",
|
|
2304
2305
|
"onChange": {
|
|
@@ -16326,7 +16327,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
16326
16327
|
"type": "TextField",
|
|
16327
16328
|
"props": {
|
|
16328
16329
|
"placeholder": "Password",
|
|
16329
|
-
"
|
|
16330
|
+
"valuePath": "$service:himiEventsRsvp.password",
|
|
16330
16331
|
"secure": true,
|
|
16331
16332
|
"autoCapitalization": "none",
|
|
16332
16333
|
"onChange": {
|