@ofidj/entry 3.10.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,10 +26,86 @@ renders, so there is one implementation and no hand-porting.
26
26
  npm install @ofidj/entry
27
27
  ```
28
28
 
29
- ```ts
30
- import { providerEntry, bindAgreement, acceptedAgreement } from "@ofidj/entry";
29
+ Three entry points, split by what they need to run:
30
+
31
+ | Import | Needs | What it holds |
32
+ | --------------------- | ---------------- | ------------------------------------------------------------------------------ |
33
+ | `@ofidj/entry` | nothing | **the model**: every rule, and every sentence a person is shown, as plain data |
34
+ | `@ofidj/entry/dom` | a document | one renderer of that model as HTML strings, and the helpers that drive it |
35
+ | `@ofidj/entry/window` | a browser window | the Fidj window and the answer it relays back |
36
+
37
+ The root is pure — no DOM, no markup, no view layer. That is deliberate: a
38
+ screen is a **value**, so the same entry can be drawn as HTML strings, as React
39
+ components or as Vue single-file components, and none of them re-derives a rule
40
+ or re-types a sentence.
41
+
42
+ ### On React, Vue, Svelte, or a server
43
+
44
+ Take the model and render it yourself. Nothing here assumes a browser, so it
45
+ works under SSR, in a test, or in a plain Node script.
46
+
47
+ ```tsx
48
+ import {
49
+ agreementModel,
50
+ acceptance,
51
+ agreementRequired,
52
+ agreementFromRefusal,
53
+ verificationPending,
54
+ pollVerification,
55
+ signInErrorMessage,
56
+ } from "@ofidj/entry";
57
+ import { openProviderWindow } from "@ofidj/entry/window";
31
58
  import "@ofidj/entry/tokens.css";
32
59
  import "@ofidj/entry/style.css";
60
+
61
+ function Agreement({ title, agreement, onAccepted }) {
62
+ const [checked, setChecked] = useState(false);
63
+ const screen = agreementModel(title, agreement, { checked });
64
+ return (
65
+ <form
66
+ onSubmit={() =>
67
+ onAccepted(acceptance({ checked, version: screen.version }))
68
+ }
69
+ >
70
+ <h2>{screen.heading}</h2>
71
+ <p className="signin-lead">{screen.lead}</p>
72
+ <p className="fineprint">{screen.versionLabel}</p>
73
+ <div className="agreement-text" tabIndex={0}>
74
+ {screen.text}
75
+ </div>
76
+ <label className="agreement-choice">
77
+ <input
78
+ type="checkbox"
79
+ checked={checked}
80
+ onChange={(e) => setChecked(e.target.checked)}
81
+ />
82
+ <span>{screen.checkboxLabel}</span>
83
+ </label>
84
+ <button className="primary" disabled={screen.submitDisabled}>
85
+ {screen.submitLabel}
86
+ </button>
87
+ </form>
88
+ );
89
+ }
90
+ ```
91
+
92
+ `acceptance()` is the rule `@ofidj/entry/dom` applies to a checkbox, asked of
93
+ your own state instead: a tick is evidence only if it was live and only if it
94
+ names the version that was displayed. Both callers have to get the same answer,
95
+ or the evidence means different things depending on who collected it.
96
+
97
+ Authentication itself is not here — that is `@ofidj/node`. This package is the
98
+ screens and the flow around them.
99
+
100
+ ### On a shell that builds pages out of strings
101
+
102
+ ```ts
103
+ import { agreementRequired, agreementFromRefusal } from "@ofidj/entry";
104
+ import {
105
+ agreementScreen,
106
+ bindAgreementScreen,
107
+ acceptedAgreement,
108
+ } from "@ofidj/entry/dom";
33
109
  ```
34
110
 
35
111
  `tokens.css` defines every colour, family and radius the product paints with;
@@ -39,24 +115,81 @@ prefix.
39
115
 
40
116
  ## What is here, and what is not
41
117
 
42
- Moved, and covered by characterization tests in `test/`:
118
+ | Module | What it holds |
119
+ | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
120
+ | `model.ts` | the refusals and what they mean, the acceptance rule, and a model per screen: entry, agreement, verification wait, account, credentials |
121
+ | `remembered.ts` | the address this browser remembers, and forgetting it |
122
+ | `dom.ts` | the HTML renderer, the agreement binding, the folding form, the `fidj@<version>` badge |
123
+ | `provider-window.ts` | the Fidj window and the answer it relays back |
124
+ | `tokens.css`, `fonts.css`, `style.css` | the design system |
125
+
126
+ Two tests hold the split, and they are the reason it stays true. One exercises
127
+ every model function in a file that never loads jsdom and never defines `window`
128
+ or `document`. The other asserts the HTML renderer says **what the model says**
129
+ and invents nothing — because a sentence able to drift between the two is a
130
+ React app and a generated app telling the same person different things, which is
131
+ the failure this package exists to end.
132
+
133
+ Still in `generator-fidj`'s template, and next to move: the Fidj-hosted OIDC
134
+ interaction page. It imports `app.config.json` directly, so it moves behind a
135
+ config argument rather than verbatim.
43
136
 
44
- | Module | What it holds |
45
- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
46
- | `service-agreement.ts` | the agreement block and its loading, `providerEntry`'s three shapes, the remembered address, sign-in error wording |
47
- | `provider-window.ts` | the Fidj window and the answer it relays back |
48
- | `version.ts` | the `fidj@<version>` badge |
49
- | `tokens.css`, `fonts.css`, `style.css` | the design system |
137
+ ## The entry flow
50
138
 
51
- Still in `generator-fidj`'s template, and next to move: the screens inside
52
- `content.ts` — the sign-in entry, `#/forgot`, `#/reset`, `#/verify`, `#/account`
53
- and the Fidj-hosted OIDC interaction page. They import `app.config.json`
54
- directly, so they move behind a config argument rather than verbatim.
139
+ [The workspace README](../README.md#entry-one-flow-the-same-everywhere) defines
140
+ it; this package implements it, so every surface gets the same one.
141
+
142
+ ```ts
143
+ import {
144
+ pollVerification, // the wait ends by itself when the link is opened
145
+ agreementRequired, // was this refusal "you owe this app its agreement"?
146
+ agreementFromRefusal, // ...and the agreement it refused with
147
+ verificationPending, // was it "nobody has proved this address yet"?
148
+ credentialsModel, // email, password, two buttons, nothing gated
149
+ verificationWaitModel, // the wait a new account owes its address
150
+ agreementModel, // the second screen, and when its submit is live
151
+ } from "@ofidj/entry";
152
+ import {
153
+ credentialFields,
154
+ verificationWait,
155
+ agreementScreen,
156
+ bindAgreementScreen,
157
+ } from "@ofidj/entry/dom"; // ...the same screens, as markup
158
+ ```
55
159
 
56
- **The generator has not switched over yet.** Its template still carries its own
57
- copies and builds from them, so a change made here reaches nobody until that
58
- lands. `test/no-drift-from-the-generator.test.mjs` fails while the two disagree,
59
- and is deleted in the same change that deletes the generator's copies.
160
+ **The API decides, not the screen.** Call `login`, and when it refuses with
161
+ `409 agreement_required`, show the agreement screen. A screen that decided for
162
+ itself
163
+ would have to know which version the account already accepted, which is the
164
+ API's to know — and an owner publishing a new version is what makes the
165
+ question owed again.
166
+
167
+ **Read the agreement off the refusal.** `agreementFromRefusal` returns the
168
+ version and text the API compared against, from `FidjError.details`. Fetching it
169
+ again with `GET /apps/:appId` usually gives the same answer and occasionally
170
+ not: an owner who publishes between the refusal and the refetch would have
171
+ somebody accept text the API is about to call stale. It needs `@ofidj/node`
172
+ 3.10.2 or later, where `login` stopped rebuilding its error from
173
+ `err.toString()` and losing the body; against an older SDK it returns `null` and
174
+ the caller falls back to fetching.
175
+
176
+ **The wait is not a screen.** `verificationWait` returns a block to put _under_
177
+ the credential form, which stays where it is. That is what keeps a mistyped
178
+ address correctable: the field is still there, still filled in — correcting it
179
+ and creating the account again is the repair, and it reads as one because
180
+ nothing moved.
181
+
182
+ **`pollVerification` treats a failed check as no answer.** A browser that lost
183
+ its connection has learned nothing about the address, so the wait continues
184
+ rather than reporting anything.
185
+
186
+ ### What went with the old arrangement
187
+
188
+ `agreementMarkup` and `bindAgreement` are gone. They put the checkbox beside the
189
+ credentials and gated the submit on whether the agreement had _loaded_, ticked or
190
+ not; the generated shells both render the two screens now, so nothing called
191
+ them. `agreementScreen` and `bindAgreementScreen` replace them, and the tick is
192
+ what opens the door.
60
193
 
61
194
  ## Release
62
195
 
package/dom.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { type AccountState, type SigninShape, type VerificationState } from "./index.js";
2
+ export declare const escape: (value: unknown) => string;
3
+ export declare function masthead(logo: string, title: string): string;
4
+ export declare function highlightCells(entries?: Array<{
5
+ heading: string;
6
+ body: string;
7
+ }>): string;
8
+ export declare function badgeStrip(entries?: string[]): string;
9
+ export declare function credentialFields(state: {
10
+ email: string;
11
+ password: string;
12
+ }): string;
13
+ export declare function accountForm(route: string, state: AccountState): string;
14
+ export declare function returnNotice(asking: string): string;
15
+ export declare function providerEntry(title: string, appId: string, credentials: string, isFidjItself?: boolean, shape?: SigninShape): string;
16
+ export declare function agreementScreen(title: string, agreement: {
17
+ version?: string;
18
+ text?: string;
19
+ }): string;
20
+ export declare function verificationWait(state: VerificationState): string;
21
+ export declare function acceptedAgreement(form: HTMLFormElement): {
22
+ termsAccepted: boolean;
23
+ termsVersion: string;
24
+ } | null;
25
+ export declare function bindAgreementScreen(form: HTMLFormElement | null): void;
26
+ export declare function showEmailEntry(open: boolean, focus?: boolean): void;
27
+ export declare function showVersionBadge(version: string, apiEndpoint?: string): void;
package/dom.js ADDED
@@ -0,0 +1,224 @@
1
+ // The entry, as markup and as behaviour.
2
+ //
3
+ // One renderer of the model in `@ofidj/entry`, for the shells that build pages
4
+ // out of strings. Every sentence on these screens comes from the model rather
5
+ // than from here — that is the property worth keeping, because it is what makes
6
+ // a React or Vue app render the *same* entry instead of a similar one.
7
+ //
8
+ // What stayed out of the model is the shell that holds the screens: the sign-in
9
+ // screen nests signin-trust inside its inner div and the interaction screen
10
+ // makes it a sibling, so a single shell builder would have had to change one of
11
+ // them. That is a decision, not a move.
12
+ import { accountModel, agreementModel, credentialsModel, providerEntryModel, returnNoticeModel, signInHint, verificationWaitModel, } from "./index.js";
13
+ export const escape = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]);
14
+ const attribute = (name, value) => value === undefined || value === false || value === ""
15
+ ? ""
16
+ : ` ${name}="${escape(value)}"`;
17
+ const button = (control, extraClass = "") => {
18
+ const classes = [
19
+ control.kind === "primary"
20
+ ? "primary"
21
+ : control.kind === "secondary"
22
+ ? "secondary"
23
+ : "quiet",
24
+ extraClass,
25
+ ]
26
+ .filter(Boolean)
27
+ .join(" ");
28
+ // A control the entry's own form posts is a submit; one that only changes
29
+ // what is on screen is not, and saying so is what keeps a disclosure from
30
+ // opening a second window.
31
+ const type = control.name ? "submit" : "button";
32
+ return `<button class="${classes}" type="${type}"${attribute("id", control.name ? "" : control.id)}${attribute("name", control.name)}${attribute("value", control.value)}>${escape(control.label)}</button>`;
33
+ };
34
+ // The app's own mark beside its own name, decorative because the name is right
35
+ // there: every shell opens with this.
36
+ export function masthead(logo, title) {
37
+ return `<header class="signin-masthead"><img class="app-mark" src="${escape(logo)}" alt=""><strong>${escape(title)}</strong></header>`;
38
+ }
39
+ export function highlightCells(entries) {
40
+ if (!entries?.length)
41
+ return "";
42
+ return `<div class="signin-highlights">${entries
43
+ .map((entry, index) => `<article><p class="eyebrow">${String(index + 1).padStart(2, "0")}</p><h2>${escape(entry.heading)}</h2><p>${escape(entry.body)}</p></article>`)
44
+ .join("")}</div>`;
45
+ }
46
+ export function badgeStrip(entries) {
47
+ if (!entries?.length)
48
+ return "";
49
+ return `<footer class="signin-badges">${entries
50
+ .map((entry) => `<span>${escape(entry)}</span>`)
51
+ .join("")}</footer>`;
52
+ }
53
+ export function credentialFields(state) {
54
+ const model = credentialsModel(state);
55
+ return (`<label for="email">${escape(model.email.label)}</label><input id="email" type="email" value="${escape(model.email.value)}" placeholder="${escape(model.email.placeholder)}" autocomplete="username">` +
56
+ `<div class="field-head"><label for="password">${escape(model.password.label)}</label><a href="${escape(model.forgot.href)}">${escape(model.forgot.label)}</a></div>` +
57
+ `<div class="password-field"><input id="password" type="password" value="${escape(model.password.value)}" placeholder="${escape(model.password.placeholder)}" autocomplete="current-password"><button type="button" id="reveal" aria-controls="password">${escape(model.reveal.label)}</button></div>` +
58
+ `<button class="primary" type="submit" name="entry" value="credentials">${escape(model.submit.label)}</button>` +
59
+ `<button class="secondary" type="submit" name="signup" value="true">${escape(model.signup.label)}</button>`);
60
+ }
61
+ // The four account screens, drawn from the model that decides which of them has
62
+ // a form to show.
63
+ export function accountForm(route, state) {
64
+ const model = accountModel(route, state);
65
+ const fields = model.fields
66
+ .map((field) => `<label for="${escape(field.id)}">${escape(field.label)}</label><input id="${escape(field.id)}" type="${escape(field.type)}"${attribute("autocomplete", field.autocomplete)}${attribute("minlength", field.minlength)}${field.required ? " required" : ""}>`)
67
+ .join("");
68
+ const form = model.submitLabel
69
+ ? `<form id="recovery">${fields}${model.hint ? `<p>${escape(model.hint)}</p>` : ""}<button class="primary">${escape(model.submitLabel)}</button></form>`
70
+ : "";
71
+ const alternative = model.alternative
72
+ ? `${model.alternative.text ? `<p>${escape(model.alternative.text)}</p>` : ""}${model.alternative.href ? `<a href="${escape(model.alternative.href)}">${escape(model.alternative.label)}</a>` : ""}`
73
+ : "";
74
+ if (route === "forgot" || route === "reset" || route === "verify")
75
+ return `<h2>${escape(model.heading)}</h2><p>${escape(model.intro)}</p>${form}${alternative}`;
76
+ // My account keeps its own shape: an identity line, a status the page
77
+ // refreshes in place, and the controls that act on it.
78
+ return (`<h2>${escape(model.heading)}</h2>` +
79
+ `<p class="account-identity">Signed in as <strong>${escape(state.accountEmail)}</strong></p>` +
80
+ `<p>${escape(model.intro)}</p>` +
81
+ `<p id="verification-status">${escape(model.status)}</p>` +
82
+ model.extras
83
+ .map((control) => `<button id="${escape(control.id)}">${escape(control.label)}</button>`)
84
+ .join("") +
85
+ `<p><a href="${escape(model.alternative.href)}">${escape(model.alternative.label)}</a></p>`);
86
+ }
87
+ export function returnNotice(asking) {
88
+ return `<p class="signin-return" role="note">${escape(returnNoticeModel(asking))}</p>`;
89
+ }
90
+ // The entry every app that delegates to the provider renders. The shape logic
91
+ // lives in the model; what is here is the markup and the one browser fact the
92
+ // model refuses to reach for itself — what this browser remembers.
93
+ export function providerEntry(title, appId, credentials, isFidjItself = false, shape = "button") {
94
+ const model = providerEntryModel({
95
+ title,
96
+ hint: signInHint(appId),
97
+ isFidjItself,
98
+ shape,
99
+ hasCredentials: Boolean(credentials),
100
+ });
101
+ const lead = `<p class="signin-lead">${escape(model.lead)}</p>`;
102
+ if (!model.door)
103
+ return lead + credentials;
104
+ const door = button(model.door, "fidj-entry");
105
+ const forget = model.forget
106
+ ? `<button type="button" id="${escape(model.forget.id)}" class="quiet">${escape(model.forget.label)}</button>`
107
+ : "";
108
+ if (!model.disclosure)
109
+ return lead + door + forget;
110
+ // Both doors. The app's own one is a second thought for the person who wants
111
+ // it, which is what a disclosure is for: it costs one click and takes nothing
112
+ // away, where a second button of equal weight cost everybody a decision.
113
+ return (lead +
114
+ door +
115
+ forget +
116
+ `<div class="signin-alternate"><button type="button" id="use-email" class="signin-toggle" aria-expanded="${model.disclosure.expanded}" aria-controls="${escape(model.disclosure.controls)}"><span class="signin-toggle-label">${escape(model.disclosure.label)}<span class="caret" aria-hidden="true"></span></span></button>
117
+ <div id="${escape(model.disclosure.controls)}" hidden>${credentials}</div></div>`);
118
+ }
119
+ // Screen three, drawn. The version travels on the checkbox so that what is
120
+ // recorded is what was displayed.
121
+ export function agreementScreen(title, agreement) {
122
+ const model = agreementModel(title, agreement);
123
+ return `<h2>${escape(model.heading)}</h2>
124
+ <p class="signin-lead">${escape(model.lead)}</p>
125
+ <p class="fineprint">${escape(model.versionLabel)}</p>
126
+ <div class="agreement-text" tabindex="0">${escape(model.text)}</div>
127
+ <label class="agreement-choice"><input id="service-agreement" type="checkbox" required aria-required="true" data-version="${escape(model.version)}"><span>${escape(model.checkboxLabel)}</span></label>
128
+ <button class="primary" type="submit"${model.submitDisabled ? " disabled" : ""}>${escape(model.submitLabel)}</button>`;
129
+ }
130
+ // Screen two, drawn under the form that started it.
131
+ export function verificationWait(state) {
132
+ const model = verificationWaitModel(state);
133
+ const notice = model.notice
134
+ ? model.notice.kind === "error"
135
+ ? `<p role="alert" class="error">${escape(model.notice.text)}</p>`
136
+ : `<p class="fineprint">${escape(model.notice.text)}</p>`
137
+ : "";
138
+ return `<div class="verification-wait">
139
+ <p role="status"><span class="spinner" aria-hidden="true"></span>${escape(model.status)} <strong>${escape(model.email)}</strong>.</p>
140
+ <p class="fineprint">${escape(model.fineprint)}</p>
141
+ ${notice}
142
+ <button type="button" id="resend-verification">${escape(model.resendLabel)}</button>
143
+ </div>`;
144
+ }
145
+ // ---------------------------------------------------------------------------
146
+ // Reading and driving a real document
147
+ // ---------------------------------------------------------------------------
148
+ // The acceptance, read off the form that collected it. The rule it applies is
149
+ // `acceptance()` in the model; what is here is where the three facts are found.
150
+ export function acceptedAgreement(form) {
151
+ const checkbox = form.querySelector("#service-agreement");
152
+ if (!checkbox?.checked || checkbox.disabled || !checkbox.dataset.version)
153
+ return null;
154
+ return { termsAccepted: true, termsVersion: checkbox.dataset.version };
155
+ }
156
+ // The tick is what opens the door, and unticking closes it again. This is the
157
+ // rule generator-fidj's README always claimed and its code never had:
158
+ // bindAgreement gated on whether the agreement had *loaded*, which is the right
159
+ // rule for a checkbox standing beside a password and the wrong one for a screen
160
+ // that exists only to collect it.
161
+ export function bindAgreementScreen(form) {
162
+ if (!form)
163
+ return;
164
+ const checkbox = form.querySelector("#service-agreement");
165
+ if (!checkbox)
166
+ return;
167
+ const submits = form.querySelectorAll('button[type="submit"]');
168
+ const update = () => submits.forEach((element) => {
169
+ element.disabled = !checkbox.checked;
170
+ });
171
+ checkbox.addEventListener("change", update);
172
+ update();
173
+ }
174
+ // Opening the app's own form takes the space the Fidj door was using.
175
+ //
176
+ // The two are alternatives, not a list, and watching one fold away as the other
177
+ // arrives is what says so — where a form appearing underneath a button that is
178
+ // still there reads as "and also". The toggle stays put through both states, so
179
+ // it ends up labelling whichever one is on screen: a caret pointing down at a
180
+ // form that is not here yet, and up at the one it would put away.
181
+ export function showEmailEntry(open, focus = false) {
182
+ const fields = document.getElementById("email-entry");
183
+ const toggle = document.getElementById("use-email");
184
+ if (!fields || !toggle)
185
+ return;
186
+ toggle.setAttribute("aria-expanded", String(open));
187
+ fields.hidden = !open;
188
+ const door = document.querySelector(".fidj-entry");
189
+ if (door) {
190
+ door.classList.toggle("is-folded", open);
191
+ // Out of the tab order and out of the accessibility tree the moment it
192
+ // starts leaving: something mid-fold is not something to press, and an
193
+ // animation is not what decides that.
194
+ if (open)
195
+ door.setAttribute("aria-hidden", "true");
196
+ else
197
+ door.removeAttribute("aria-hidden");
198
+ door.tabIndex = open ? -1 : 0;
199
+ }
200
+ if (open && focus)
201
+ document.getElementById("email")?.focus();
202
+ }
203
+ export function showVersionBadge(version, apiEndpoint) {
204
+ if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(version || ""))
205
+ return;
206
+ const badge = document.createElement("div");
207
+ badge.className = "fidj-version";
208
+ badge.setAttribute("aria-label", `Fidj version ${version}`);
209
+ badge.textContent = `fidj@${version}`;
210
+ document.body.append(badge);
211
+ if (apiEndpoint) {
212
+ void fetch(`${apiEndpoint.replace(/\/$/, "")}/status`)
213
+ .then((response) => (response.ok ? response.json() : null))
214
+ .then((status) => {
215
+ const apiVersion = status?.version || status?.built;
216
+ if (!apiVersion)
217
+ return;
218
+ badge.textContent = `fidj@${version} · API ${apiVersion}`;
219
+ badge.setAttribute("aria-label", `Fidj version ${version}, API version ${apiVersion}`);
220
+ })
221
+ .catch(() => undefined);
222
+ }
223
+ }
224
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG9tLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2RvbS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSx5Q0FBeUM7QUFDekMsRUFBRTtBQUNGLCtFQUErRTtBQUMvRSw4RUFBOEU7QUFDOUUsZ0ZBQWdGO0FBQ2hGLHVFQUF1RTtBQUN2RSxFQUFFO0FBQ0YsZ0ZBQWdGO0FBQ2hGLDRFQUE0RTtBQUM1RSxnRkFBZ0Y7QUFDaEYsd0NBQXdDO0FBRXhDLE9BQU8sRUFDTCxZQUFZLEVBQ1osY0FBYyxFQUNkLGdCQUFnQixFQUNoQixrQkFBa0IsRUFDbEIsaUJBQWlCLEVBQ2pCLFVBQVUsRUFDVixxQkFBcUIsR0FLdEIsTUFBTSxZQUFZLENBQUM7QUFFcEIsTUFBTSxDQUFDLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBYyxFQUFFLEVBQUUsQ0FDdkMsTUFBTSxDQUFDLEtBQUssSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQ3pCLFVBQVUsRUFDVixDQUFDLElBQUksRUFBRSxFQUFFLENBQ1AsQ0FBQyxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxRQUFRLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQ3ZFLElBQUksQ0FDSixDQUNMLENBQUM7QUFFSixNQUFNLFNBQVMsR0FBRyxDQUFDLElBQVksRUFBRSxLQUFpQyxFQUFFLEVBQUUsQ0FDcEUsS0FBSyxLQUFLLFNBQVMsSUFBSSxLQUFLLEtBQUssS0FBSyxJQUFJLEtBQUssS0FBSyxFQUFFO0lBQ3BELENBQUMsQ0FBQyxFQUFFO0lBQ0osQ0FBQyxDQUFDLElBQUksSUFBSSxLQUFLLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDO0FBRXBDLE1BQU0sTUFBTSxHQUFHLENBQUMsT0FBcUIsRUFBRSxVQUFVLEdBQUcsRUFBRSxFQUFFLEVBQUU7SUFDeEQsTUFBTSxPQUFPLEdBQUc7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
package/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
- export { agreementMarkup, acceptedAgreement, signInErrorMessage, bindAgreement, providerEntry, signInHint, rememberSignIn, forgetSignIn, showEmailEntry, type SigninShape, } from "./service-agreement.js";
2
- export { openProviderWindow, relayProviderAnswer, type ProviderWindow, } from "./provider-window.js";
3
- export { showVersionBadge } from "./version.js";
1
+ export { agreementRequired, agreementFromRefusal, verificationPending, signInErrorMessage, acceptance, providerEntryModel, agreementModel, verificationWaitModel, accountModel, credentialsModel, returnNoticeModel, pollVerification, type SigninShape, type EntryControl, type ProviderEntryModel, type AgreementModel, type VerificationState, type VerificationWaitModel, type AccountState, type AccountField, type AccountModel, type CredentialsModel, } from "./model.js";
2
+ export { signInHint, rememberSignIn, forgetSignIn } from "./remembered.js";
package/index.js CHANGED
@@ -1,11 +1,16 @@
1
- // The Fidj entry, in one place.
1
+ // The Fidj entry: the rules, and every sentence a person is shown.
2
2
  //
3
- // Everything here was lifted verbatim out of generator-fidj's template, where
4
- // it could only ship by being generated. It is a package now so that the
5
- // console can import the same screens a generated app renders, instead of the
6
- // two being kept in step by hand — which is how the service agreement ended up
7
- // existing in four copies that no test could diff.
8
- export { agreementMarkup, acceptedAgreement, signInErrorMessage, bindAgreement, providerEntry, signInHint, rememberSignIn, forgetSignIn, showEmailEntry, } from "./service-agreement.js";
9
- export { openProviderWindow, relayProviderAnswer, } from "./provider-window.js";
10
- export { showVersionBadge } from "./version.js";
11
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsZ0NBQWdDO0FBQ2hDLEVBQUU7QUFDRiw4RUFBOEU7QUFDOUUseUVBQXlFO0FBQ3pFLDhFQUE4RTtBQUM5RSwrRUFBK0U7QUFDL0UsbURBQW1EO0FBQ25ELE9BQU8sRUFDTCxlQUFlLEVBQ2YsaUJBQWlCLEVBQ2pCLGtCQUFrQixFQUNsQixhQUFhLEVBQ2IsYUFBYSxFQUNiLFVBQVUsRUFDVixjQUFjLEVBQ2QsWUFBWSxFQUNaLGNBQWMsR0FFZixNQUFNLHdCQUF3QixDQUFDO0FBQ2hDLE9BQU8sRUFDTCxrQkFBa0IsRUFDbEIsbUJBQW1CLEdBRXBCLE1BQU0sc0JBQXNCLENBQUM7QUFDOUIsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sY0FBYyxDQUFDIn0=
3
+ // This root is pure. No DOM, no markup, no view layer — a screen is a value, so
4
+ // the same entry can be drawn as HTML strings, as React components or as Vue
5
+ // single-file components without any of them re-deriving a rule or re-typing a
6
+ // sentence. That is what the package is for: these screens used to live only in
7
+ // generator-fidj's template, where shipping them meant generating an app, and
8
+ // the alternative — keeping a second copy in step by hand — is what produced
9
+ // four versions of the service agreement that no test could diff.
10
+ //
11
+ // Two subpaths carry what cannot be pure:
12
+ // @ofidj/entry/dom the HTML renderer and the helpers that drive a document
13
+ // @ofidj/entry/window the Fidj window and the answer it relays back
14
+ export { agreementRequired, agreementFromRefusal, verificationPending, signInErrorMessage, acceptance, providerEntryModel, agreementModel, verificationWaitModel, accountModel, credentialsModel, returnNoticeModel, pollVerification, } from "./model.js";
15
+ export { signInHint, rememberSignIn, forgetSignIn } from "./remembered.js";
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsbUVBQW1FO0FBQ25FLEVBQUU7QUFDRixnRkFBZ0Y7QUFDaEYsNkVBQTZFO0FBQzdFLCtFQUErRTtBQUMvRSxnRkFBZ0Y7QUFDaEYsOEVBQThFO0FBQzlFLDZFQUE2RTtBQUM3RSxrRUFBa0U7QUFDbEUsRUFBRTtBQUNGLDBDQUEwQztBQUMxQyxpRkFBaUY7QUFDakYsdUVBQXVFO0FBQ3ZFLE9BQU8sRUFDTCxpQkFBaUIsRUFDakIsb0JBQW9CLEVBQ3BCLG1CQUFtQixFQUNuQixrQkFBa0IsRUFDbEIsVUFBVSxFQUNWLGtCQUFrQixFQUNsQixjQUFjLEVBQ2QscUJBQXFCLEVBQ3JCLFlBQVksRUFDWixnQkFBZ0IsRUFDaEIsaUJBQWlCLEVBQ2pCLGdCQUFnQixHQVdqQixNQUFNLFlBQVksQ0FBQztBQUNwQixPQUFPLEVBQUUsVUFBVSxFQUFFLGNBQWMsRUFBRSxZQUFZLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQyJ9
package/model.d.ts ADDED
@@ -0,0 +1,131 @@
1
+ export type SigninShape = "button" | "inline" | "both";
2
+ export declare function agreementRequired(error: unknown): boolean;
3
+ export declare function agreementFromRefusal(error: unknown): {
4
+ version: string;
5
+ text: string;
6
+ } | null;
7
+ export declare function verificationPending(error: unknown): {
8
+ email: string;
9
+ } | null;
10
+ export declare function signInErrorMessage(error: unknown): "Too many attempts. Please wait before trying again." | "We could not sign in to this account. Check the email and password." | "An account already uses this email. Check the password or sign in instead." | "We cannot reach Fidj right now. Please try again." | "We could not sign in to this account. Please try again.";
11
+ export declare function acceptance(state: {
12
+ checked?: boolean;
13
+ disabled?: boolean;
14
+ version?: string;
15
+ }): {
16
+ termsAccepted: true;
17
+ termsVersion: string;
18
+ } | null;
19
+ export type EntryControl = {
20
+ label: string;
21
+ id: string;
22
+ name?: string;
23
+ value?: string;
24
+ kind: "primary" | "secondary" | "quiet";
25
+ };
26
+ export type ProviderEntryModel = {
27
+ shape: SigninShape;
28
+ lead: string;
29
+ door: EntryControl | null;
30
+ forget: EntryControl | null;
31
+ disclosure: {
32
+ label: string;
33
+ controls: string;
34
+ expanded: boolean;
35
+ } | null;
36
+ };
37
+ export declare function providerEntryModel(options: {
38
+ title: string;
39
+ hint?: string;
40
+ isFidjItself?: boolean;
41
+ shape?: SigninShape;
42
+ hasCredentials?: boolean;
43
+ }): ProviderEntryModel;
44
+ export type AgreementModel = {
45
+ heading: string;
46
+ lead: string;
47
+ versionLabel: string;
48
+ version: string;
49
+ text: string;
50
+ checkboxLabel: string;
51
+ submitLabel: string;
52
+ submitDisabled: boolean;
53
+ };
54
+ export declare function agreementModel(title: string, agreement: {
55
+ version?: string;
56
+ text?: string;
57
+ }, state?: {
58
+ checked?: boolean;
59
+ }): AgreementModel;
60
+ export type VerificationState = {
61
+ email: string;
62
+ resent?: boolean;
63
+ error?: string;
64
+ };
65
+ export type VerificationWaitModel = {
66
+ status: string;
67
+ email: string;
68
+ fineprint: string;
69
+ notice: {
70
+ kind: "error" | "info";
71
+ text: string;
72
+ } | null;
73
+ resendLabel: string;
74
+ };
75
+ export declare function verificationWaitModel(state: VerificationState): VerificationWaitModel;
76
+ export type AccountState = {
77
+ linkToken?: string;
78
+ verificationConfirmed?: boolean;
79
+ emailVerified?: boolean;
80
+ accountEmail?: string;
81
+ };
82
+ export type AccountField = {
83
+ id: string;
84
+ label: string;
85
+ type: string;
86
+ autocomplete?: string;
87
+ minlength?: number;
88
+ required?: boolean;
89
+ };
90
+ export type AccountModel = {
91
+ heading: string;
92
+ intro: string;
93
+ fields: AccountField[];
94
+ hint: string;
95
+ submitLabel: string;
96
+ alternative: {
97
+ text: string;
98
+ href?: string;
99
+ label?: string;
100
+ } | null;
101
+ status: string;
102
+ extras: EntryControl[];
103
+ };
104
+ export declare function accountModel(route: string, state: AccountState): AccountModel;
105
+ export type CredentialsModel = {
106
+ email: AccountField & {
107
+ value: string;
108
+ placeholder: string;
109
+ };
110
+ password: AccountField & {
111
+ value: string;
112
+ placeholder: string;
113
+ };
114
+ forgot: {
115
+ href: string;
116
+ label: string;
117
+ };
118
+ reveal: EntryControl;
119
+ submit: EntryControl;
120
+ signup: EntryControl;
121
+ };
122
+ export declare function credentialsModel(state: {
123
+ email: string;
124
+ password: string;
125
+ }): CredentialsModel;
126
+ export declare function returnNoticeModel(asking: string): string;
127
+ export declare function pollVerification(options: {
128
+ check: () => Promise<boolean>;
129
+ onVerified: () => void;
130
+ intervalMs?: number;
131
+ }): () => void;