@antelopejs/interface-dms 0.7.2 → 0.7.3

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 (31) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/base/block-registry/index.d.ts +1 -0
  3. package/dist/base/block-registry/index.js +1 -0
  4. package/dist/base/block-registry/index.js.map +1 -1
  5. package/dist/base/block-registry/presentation.d.ts +21 -0
  6. package/dist/base/block-registry/presentation.js +108 -0
  7. package/dist/base/block-registry/presentation.js.map +1 -0
  8. package/dist/base/placeholder.js +2 -2
  9. package/dist/base/placeholder.js.map +1 -1
  10. package/docs/1.core/2.pages-and-modules.md +2 -0
  11. package/docs/1.core/5.tenancy.md +2 -0
  12. package/docs/3.base/1.introduction.md +1 -1
  13. package/docs/3.base/6.layout.md +7 -1
  14. package/docs/4.notifications/1.introduction.md +2 -0
  15. package/docs/4.notifications/2.categories-and-subjects.md +2 -0
  16. package/docs/4.notifications/3.sending.md +2 -0
  17. package/docs/5.html-render/1.introduction.md +14 -0
  18. package/docs/5.html-render/2.built-in-emails.md +50 -0
  19. package/docs/images/emails-admin-invite.png +0 -0
  20. package/docs/images/emails-change-notice.png +0 -0
  21. package/docs/images/emails-change-verification.png +0 -0
  22. package/docs/images/emails-digest-logo-dark.png +0 -0
  23. package/docs/images/emails-digest-logo-light.png +0 -0
  24. package/docs/images/emails-digest.png +0 -0
  25. package/docs/images/emails-export-partial.png +0 -0
  26. package/docs/images/emails-export-ready.png +0 -0
  27. package/docs/images/emails-reset-password.png +0 -0
  28. package/docs/images/emails-two-factor.png +0 -0
  29. package/docs/images/emails-user-validation.png +0 -0
  30. package/docs/index.md +9 -0
  31. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.7.3
4
+
5
+ [compare changes](https://github.com/AntelopeJS/dms/compare/interface-v0.7.2...v0.7.3)
6
+
7
+ ### 🚀 Enhancements
8
+
9
+ - **preview:** Share simple options and document real outcomes ([#339](https://github.com/AntelopeJS/dms/pull/339))
10
+
11
+ ### 🩹 Fixes
12
+
13
+ - **docs:** Use a sandbox-compatible temporary build database ([#326](https://github.com/AntelopeJS/dms/pull/326))
14
+ - Install DMS frontend with the root workspace ([#322](https://github.com/AntelopeJS/dms/pull/322))
15
+ - **modules:** Top-align card content and place readouts below descriptions ([#328](https://github.com/AntelopeJS/dms/pull/328))
16
+ - **modules:** Reuse registered names in the store ([#330](https://github.com/AntelopeJS/dms/pull/330))
17
+ - **playground:** Use the workspace CLI and compatible core interface ([#331](https://github.com/AntelopeJS/dms/pull/331))
18
+
19
+ ### 📖 Documentation
20
+
21
+ - Create the DMS demo through the CLI in quickstart ([#327](https://github.com/AntelopeJS/dms/pull/327))
22
+
23
+ ### 🏡 Chore
24
+
25
+ - **release:** @antelopejs/dms v0.9.3 ([1aaf5404](https://github.com/AntelopeJS/dms/commit/1aaf5404))
26
+ - **docs:** Capture temporary MongoDB startup diagnostics ([#325](https://github.com/AntelopeJS/dms/pull/325))
27
+ - **playground:** Refresh compatible dependencies ([#329](https://github.com/AntelopeJS/dms/pull/329))
28
+
29
+ ### ❤️ Contributors
30
+
31
+ - Antony Rizzitelli <rizzitelli.antony@pm.me>
32
+
3
33
  ## v0.7.2
4
34
 
5
35
  [compare changes](https://github.com/AntelopeJS/dms/compare/interface-v0.7.1...v0.7.2)
@@ -1,3 +1,4 @@
1
1
  export * from "./helpers";
2
+ export * from "./presentation";
2
3
  export * from "./registry";
3
4
  export * from "./types";
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./helpers"), exports);
18
+ __exportStar(require("./presentation"), exports);
18
19
  __exportStar(require("./registry"), exports);
19
20
  __exportStar(require("./types"), exports);
20
21
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/base/block-registry/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,4CAA0B;AAC1B,6CAA2B;AAC3B,0CAAwB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/base/block-registry/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,4CAA0B;AAC1B,iDAA+B;AAC/B,6CAA2B;AAC3B,0CAAwB"}
@@ -0,0 +1,21 @@
1
+ import type { BlockOptionUi, BlockTypeDescriptor } from "./types";
2
+ /** The hints needed to decide whether an editor offers an option in simple mode. */
3
+ export interface SimpleBlockOption {
4
+ ui?: Pick<BlockOptionUi, "hidden" | "advanced" | "derivedFrom"> & {
5
+ group?: string;
6
+ };
7
+ }
8
+ /** Shared simple-mode policy; data binding remains available in the builder. */
9
+ export declare function IsSimpleBlockOption(option: SimpleBlockOption): boolean;
10
+ /**
11
+ * Dotted paths a standalone preview can edit without replacing data or opaque
12
+ * values. Objects are descended into, never offered as JSON. Only enum arrays
13
+ * in presentation/behavior groups and closed-key scalar records are editable
14
+ * collections; rows, slots and other structural collections remain fixed.
15
+ * Scalar unions have typed controls; a rich union with an explicit scalar
16
+ * widget (e.g. translated text or chart color) offers that scalar only.
17
+ *
18
+ * Fixed factory options exclude their entire top-level subtree. The returned
19
+ * paths reference `config`; consumers must not keep another copy of the schema.
20
+ */
21
+ export declare function BlockPresentationPaths(descriptor: Pick<BlockTypeDescriptor, "config" | "fixedOptions">): string[];
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IsSimpleBlockOption = IsSimpleBlockOption;
4
+ exports.BlockPresentationPaths = BlockPresentationPaths;
5
+ /** Shared simple-mode policy; data binding remains available in the builder. */
6
+ function IsSimpleBlockOption(option) {
7
+ const ui = option.ui;
8
+ return (!ui?.hidden && !ui?.advanced && !ui?.derivedFrom && ui?.group !== "advanced");
9
+ }
10
+ const PRESENTATION_WIDGETS = new Set([
11
+ "text",
12
+ "textarea",
13
+ "number",
14
+ "range",
15
+ "switch",
16
+ "select",
17
+ "segmented",
18
+ "icon",
19
+ "color",
20
+ "url",
21
+ ]);
22
+ function isPresentation(option) {
23
+ return (IsSimpleBlockOption(option) &&
24
+ option.ui?.group !== "data" &&
25
+ !option.truncated &&
26
+ (!option.ui?.widget || PRESENTATION_WIDGETS.has(option.ui.widget)));
27
+ }
28
+ // A scalar widget can replace a union's structured value, so a restricted
29
+ // descendant in any branch forbids offering that parent too.
30
+ function hasRestrictedHints(option) {
31
+ if (!IsSimpleBlockOption(option) ||
32
+ option.ui?.group === "data" ||
33
+ (option.ui?.widget && !PRESENTATION_WIDGETS.has(option.ui.widget)))
34
+ return true;
35
+ const children = [
36
+ ...Object.values(option.properties ?? {}),
37
+ ...[option.items, option.values, option.keys].filter((child) => !!child),
38
+ ...(option.oneOf ?? []),
39
+ ...(option.allOf ?? []),
40
+ ...(option.prefixItems ?? []),
41
+ ];
42
+ return children.some(hasRestrictedHints);
43
+ }
44
+ function isScalar(option) {
45
+ if (option.truncated || option.allOf || option.prefixItems)
46
+ return false;
47
+ return (["string", "number", "boolean"].includes(option.type) ||
48
+ (option.type === "union" &&
49
+ !!option.oneOf?.length &&
50
+ option.oneOf.every(isScalar)));
51
+ }
52
+ function hasValueEditor(option) {
53
+ if (hasRestrictedHints(option))
54
+ return false;
55
+ if (isScalar(option))
56
+ return true;
57
+ if (option.type === "array") {
58
+ // Content collections can carry demonstration rows even when enum-valued.
59
+ return (["appearance", "behavior", "layout", "features"].includes(option.ui?.group ?? "") &&
60
+ !!option.items?.enum?.length &&
61
+ isScalar(option.items));
62
+ }
63
+ if (option.type === "record") {
64
+ return (!!option.keys?.enum?.length && !!option.values && isScalar(option.values));
65
+ }
66
+ // Rich text and palettes can still use the scalar widget their schema declares.
67
+ return (option.type === "union" &&
68
+ !!option.ui?.widget &&
69
+ !!option.oneOf?.some(isScalar));
70
+ }
71
+ /**
72
+ * Dotted paths a standalone preview can edit without replacing data or opaque
73
+ * values. Objects are descended into, never offered as JSON. Only enum arrays
74
+ * in presentation/behavior groups and closed-key scalar records are editable
75
+ * collections; rows, slots and other structural collections remain fixed.
76
+ * Scalar unions have typed controls; a rich union with an explicit scalar
77
+ * widget (e.g. translated text or chart color) offers that scalar only.
78
+ *
79
+ * Fixed factory options exclude their entire top-level subtree. The returned
80
+ * paths reference `config`; consumers must not keep another copy of the schema.
81
+ */
82
+ function BlockPresentationPaths(descriptor) {
83
+ const paths = [];
84
+ const walk = (config, prefix) => {
85
+ for (const [key, option] of Object.entries(config)) {
86
+ // Dots and prototype keys cannot be represented safely by path editors.
87
+ if (key.includes(".") ||
88
+ ["__proto__", "constructor", "prototype"].includes(key))
89
+ continue;
90
+ if (!prefix && Object.hasOwn(descriptor.fixedOptions ?? {}, key))
91
+ continue;
92
+ if (!isPresentation(option))
93
+ continue;
94
+ const path = prefix ? `${prefix}.${key}` : key;
95
+ if (option.allOf || option.prefixItems)
96
+ continue;
97
+ if (option.type === "object" && option.properties) {
98
+ walk(option.properties, path);
99
+ continue;
100
+ }
101
+ if (hasValueEditor(option))
102
+ paths.push(path);
103
+ }
104
+ };
105
+ walk(descriptor.config, "");
106
+ return paths;
107
+ }
108
+ //# sourceMappingURL=presentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation.js","sourceRoot":"","sources":["../../../src/base/block-registry/presentation.ts"],"names":[],"mappings":"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"}
@@ -15,11 +15,11 @@ exports.PlaceholderSchema = zod_1.z.object({
15
15
  height: (0, block_registry_1.ui)(zod_1.z
16
16
  .string()
17
17
  .optional()
18
- .describe("How tall the box is, such as 200px; left empty, it fills its place."), { label: "Height", group: "appearance" }),
18
+ .describe("How tall the box is, such as 200px; left empty, it fills its place."), { label: "Height", group: "appearance", advanced: true }),
19
19
  width: (0, block_registry_1.ui)(zod_1.z
20
20
  .string()
21
21
  .optional()
22
- .describe("How wide the box is, such as 300px; left empty, it fills its column."), { label: "Width", group: "appearance" }),
22
+ .describe("How wide the box is, such as 300px; left empty, it fills its column."), { label: "Width", group: "appearance", advanced: true }),
23
23
  });
24
24
  /**
25
25
  * Placeholder - Development Placeholder Component
@@ -1 +1 @@
1
- {"version":3,"file":"placeholder.js","sourceRoot":"","sources":["../../src/base/placeholder.ts"],"names":[],"mappings":";;;AAiEA,kCASC;AA1ED,4CAAgD;AAChD,6BAAwB;AACxB,qDAA+E;AAE/E,MAAM,0BAA0B,GAAG,iBAAiB,CAAC;AAwBrD,yCAAyC;AAC5B,QAAA,iBAAiB,GAAG,OAAC,CAAC,MAAM,CAAC;IACxC,KAAK,EAAE,IAAA,mBAAE,EAAC,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4BAA4B,CAAC,EAAE;QACtE,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,SAAS;KACjB,CAAC;IACF,MAAM,EAAE,IAAA,mBAAE,EACR,OAAC;SACE,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,qEAAqE,CACtE,EACH,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,CACzC;IACD,KAAK,EAAE,IAAA,mBAAE,EACP,OAAC;SACE,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,sEAAsE,CACvE,EACH,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CACxC;CACF,CAA+C,CAAC;AAEjD;;;;;;;;;;GAUG;AACH,SAAgB,WAAW,CACzB,OAA4B;IAE5B,OAAO,IAAI,4BAAgB,CAAqB,0BAA0B,CAAC;SACxE,OAAO,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;SACvB,IAAI,CAAC;QACJ,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,gBAAgB;KACvB,CAAC,CAAC;AACP,CAAC;AAED,IAAA,kCAAiB,EAAC;IAChB,IAAI,EAAE,aAAa;IACnB,aAAa,EAAE,0BAA0B;IACzC,MAAM,EAAE,yBAAiB;IACzB,IAAI,EAAE;QACJ,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,gBAAgB;QACtB,WAAW,EAAE,kDAAkD;QAC/D,KAAK,EAAE,QAAQ;KAChB;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"placeholder.js","sourceRoot":"","sources":["../../src/base/placeholder.ts"],"names":[],"mappings":";;;AAiEA,kCASC;AA1ED,4CAAgD;AAChD,6BAAwB;AACxB,qDAA+E;AAE/E,MAAM,0BAA0B,GAAG,iBAAiB,CAAC;AAwBrD,yCAAyC;AAC5B,QAAA,iBAAiB,GAAG,OAAC,CAAC,MAAM,CAAC;IACxC,KAAK,EAAE,IAAA,mBAAE,EAAC,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4BAA4B,CAAC,EAAE;QACtE,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,SAAS;KACjB,CAAC;IACF,MAAM,EAAE,IAAA,mBAAE,EACR,OAAC;SACE,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,qEAAqE,CACtE,EACH,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,CACzD;IACD,KAAK,EAAE,IAAA,mBAAE,EACP,OAAC;SACE,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,sEAAsE,CACvE,EACH,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,CACxD;CACF,CAA+C,CAAC;AAEjD;;;;;;;;;;GAUG;AACH,SAAgB,WAAW,CACzB,OAA4B;IAE5B,OAAO,IAAI,4BAAgB,CAAqB,0BAA0B,CAAC;SACxE,OAAO,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;SACvB,IAAI,CAAC;QACJ,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,gBAAgB;KACvB,CAAC,CAAC;AACP,CAAC;AAED,IAAA,kCAAiB,EAAC;IAChB,IAAI,EAAE,aAAa;IACnB,aAAa,EAAE,0BAA0B;IACzC,MAAM,EAAE,yBAAiB;IACzB,IAAI,EAAE;QACJ,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,gBAAgB;QACtB,WAAW,EAAE,kDAAkD;QAC/D,KAAK,EAAE,QAAQ;KAChB;CACF,CAAC,CAAC"}
@@ -9,6 +9,8 @@ description: "Pages are controller classes registered into a navigation tree of
9
9
 
10
10
  A page is a controller class. `PageController(id, options, layout?)` builds a base class bound to a URL, `@RegisterPage()` records it, and the components declared as static fields on the class become the page's serialized layout. Categories group pages into the sidebar tree, and modules group a whole set of pages and categories under their own root.
11
11
 
12
+ Building your first screen? Follow [Pages & components](/docs/building/pages-and-components) first. This reference covers registration, identifiers, and lifecycle rules. The guide also shows [page-extension placement](/docs/building/pages-and-components#extending-another-modules-page) in the running playground.
13
+
12
14
  Everything on this page lives in `@antelopejs/interface-dms/page`.
13
15
 
14
16
  Registration happens at import time: the `@RegisterPage()` decorator runs when the file is loaded, so the order in which you import page files determines the order in which pages, categories and their permissions are registered.
@@ -9,6 +9,8 @@ description: "The DMS serves several tenants from one backend."
9
9
 
10
10
  The DMS serves several tenants from one backend. Tenant-owned tables live in a per-tenant schema, and a model handle is bound to one tenant when you obtain it: `GetModel(TenantMemberModel, tenantId)` reads and writes that tenant's rows only. No query filters rows by tenant, and no table carries a `tenantId` column — the isolation is in the handle.
11
11
 
12
+ For a first implementation, follow [Multi-tenant data](/docs/auth-and-tenancy/multi-tenant): register the table in the tenant schema, protect the route, and inject a tenant-bound model. Use this reference for the schema constants and lifecycle contracts. **Resolving a tenant id is not an authorization check**; `getRequestTenantId` must run behind authentication.
13
+
12
14
  The tenancy surface spans several subpaths:
13
15
 
14
16
  | Subpath | Contains |
@@ -60,7 +60,7 @@ Within `types`, four modules are outside that subpath's barrel and are imported
60
60
  - **Forms from typed fields** — A field names a `DataType`, and the type supplies both the input component and the Zod schema; `Form` compiles the field list into one schema and ships its JSON Schema form with the component.
61
61
  - **Tables from data controllers** — `TableView` reads a controller's data-api metadata and its `@Column` declarations to build the columns, the `new` / `edit` / `view` forms, and the permission-carrying actions in one call.
62
62
  - **Server-enforced row rules and guards** — Row-action rules are a small algebra evaluated on both sides, and guards run inside the mutating routes, so what the row menu offers is what the server accepts.
63
- - **A chart and widget set with a shared period scope** — Twelve chart types plus KPI, chart and top-list cards; one `PeriodSelector` id ties them to the same range and comparison.
63
+ - **A chart and widget set with a shared period scope** — Thirteen chart types plus KPI, chart and top-list cards; one `PeriodSelector` id ties them to the same range and comparison.
64
64
  - **Layout without a frontend build** — Grids, stacks, tabs and page layouts arrange components, and `CustomComponent` names a Vue component when the vocabulary runs out.
65
65
  - **An extensible data-type registry** — `@RegisterDataType` and `@RegisterDataCompareMode` add your own types and filter operators to the catalogue the forms, columns and filters draw from.
66
66
  - **Shared prop vocabulary** — Colours, sizes, HTTP methods, action targets, readonly behaviour and watch actions are declared once in `types` and reused by every component.
@@ -9,6 +9,13 @@ description: "Grid, stacks, tabs, guard, placeholder and the page layouts that h
9
9
 
10
10
  Layout components hold other components. They come in two kinds.
11
11
 
12
+ | What you want to change | Use |
13
+ | --- | --- |
14
+ | The frame around a page: dashboard, settings, or no navigation | A page layout (`DefaultLayout`, `SettingsLayout`, `EmptyLayout`). |
15
+ | The arrangement of blocks inside the page | A container builder (`Grid`, stacks, or tabs). |
16
+
17
+ See the [settings-page screenshot](/docs/building/settings-pages) for the frame and the [container previews](/docs/components/layout-and-containers) for the blocks inside it. The reference below gives their exact contracts.
18
+
12
19
  **Containers** are builders: `Grid`, `GridRow`, `HStack`, `VStack`, `Spacer`, `Tab`, `Guard` and `Placeholder` return a `ComponentBuilder`, and you nest components inside them with `.child(id, component)`. They are assigned to a static field of a page class like any other component.
13
20
 
14
21
  **Page layouts** are not builders: `DefaultLayout`, `FormPageLayout`, `SettingsLayout` and `EmptyLayout` return a plain `ComponentInfo` and are passed as the third argument of `PageController` — or as the `layout` option of a page or category, the default of everything declared under it — to say how the page's chrome is drawn.
@@ -428,4 +435,3 @@ Since the return value is an ordinary builder, `.options()`, `.child()`, `.meta(
428
435
  `CustomComponent` is also how a table view's `formContainer.pages.*.customPage` option is used: you register your own page carrying a custom component at the slug the table view would otherwise generate — declare that slug as the entry's `urlSlug`, which is taken relative to the page as it stands — see [Tables](/docs/interfaces/base/tables).
429
436
 
430
437
  ## Next Steps
431
-
@@ -13,6 +13,8 @@ The taxonomy is two levels deep. A **category** groups related notifications, a
13
13
 
14
14
  Both levels are declared at import time: `NotificationCategory()` first, then the `NotificationSubject()` calls that sit under it. Sending is a fluent chain: `Notification()` opens a builder, the setters fill in the icon, title, description and subject, and `.build()` returns an immutable `SendableNotification` you dispatch to one user, a list of users, the holders of a set of roles, or everyone.
15
15
 
16
+ See the [notification bell before and after reading](/docs/auth-and-tenancy/authentication#read-notifications) and the [category and subject preferences](/docs/auth-and-tenancy/authentication#notification-preferences) for real dashboard examples. The demonstration sends to one disposable account and checks a second session of that account; it does not broadcast or send email.
17
+
16
18
  ## Key Features
17
19
 
18
20
  - **Two-level taxonomy** — Categories group subjects, and subjects classify notifications; the `categoryId:subjectId` pair is the key a user's preferences are stored under.
@@ -13,6 +13,8 @@ You declare categories and subjects by calling `NotificationCategory()` and `Not
13
13
 
14
14
  Declarations are values, not decorators: they run when the module that contains them is imported. Declare them at the top level of a module that is imported during startup, so the taxonomy is complete before the first notification is sent.
15
15
 
16
+ The [notification preferences walkthrough](/docs/auth-and-tenancy/authentication#notification-preferences) shows the System category expanded, its locked General subject, and the Playground category and subject switched off. The Email column is marked Soon: these controls currently manage in-app delivery, not transactional authentication emails.
17
+
16
18
  ## Declaring a Category
17
19
 
18
20
  `NotificationCategory(id, category)` registers a category and returns the resulting `NotificationCategoryInfo`.
@@ -9,6 +9,8 @@ description: "Build a notification, then send it to users, roles or everyone, wi
9
9
 
10
10
  Sending happens in two steps. `Notification()` starts a builder you fill field by field; `.build()` closes it and gives you a `SendableNotification`, which carries the dispatch methods.
11
11
 
12
+ For the visible result, see the [single-recipient notification and second-session update](/docs/auth-and-tenancy/authentication#read-notifications). The walkthrough distinguishes delivery from read state and shows [a disabled subject preventing a new delivery](/docs/auth-and-tenancy/authentication#notification-preferences).
13
+
12
14
  ```typescript
13
15
  import {
14
16
  GeneralSubject,
@@ -332,6 +332,20 @@ The `head` slot adds elements to the document head (a `<title>`, a viewport `<me
332
332
 
333
333
  The logo is `branding.logo.email.light` from the app config, or `branding.logo.default.light` without an email logo, made absolute against `public.dms.clientBaseUrl` when it starts with `/`. A recipient's mail client fetches it from the internet, and Gmail and Outlook refuse SVG: with `publicLogoOnly`, the layout leaves the logo out when it is an SVG, is not an `http(s)` URL, or is served from a host a mail client cannot reach (`localhost`, `*.local`, `*.internal`, a loopback, private or link-local address), rather than drawing a broken image. Set a PNG logo and a public `clientBaseUrl` for it to show. A `branding.logo.email.dark` goes through the same rules and, when it differs from the light one, is offered to the mail clients in dark mode: the logo is then drawn as `<picture><source media="(prefers-color-scheme: dark)" srcset="…"><img …></picture>`, and a client that ignores the source draws the light image. The same check is exported as `emailLogoUrl(logoPath, clientBaseUrl)` from `#dms-layout/app/emails/components/index`, for a template that draws its own header.
334
334
 
335
+ ### A rendered custom digest
336
+
337
+ The playground's `EmailPlaygroundDigest` reuses this layout with a preheader, `divider="false"` and `public-logo-only`. Here is its actual rendered HTML in English, with fictitious activity for Alex Morgan. With the default SVG logo, the header image is intentionally absent.
338
+
339
+ ![Rendered custom playground digest without a logo or footer divider](../images/emails-digest.png)
340
+
341
+ The same template below uses two demonstration PNG logos supplied through the app config. The light-mode capture selects the teal logo; the dark-mode capture selects the navy logo. The reserved demo hostname is mapped to a local image server for these captures, so they do not establish that a recipient can fetch the logo from the internet.
342
+
343
+ ![Custom digest with the light demonstration logo in browser light mode](../images/emails-digest-logo-light.png)
344
+
345
+ ![Custom digest with the dark demonstration logo in browser dark mode](../images/emails-digest-logo-dark.png)
346
+
347
+ Dark mode changes the selected logo, not the card: `EmailLayout` keeps its white card and pale background in both browser modes. Without a dark logo source, the unbranded examples look identical in both modes. These captures use the real frontend render endpoint and show the returned HTML directly, not the playground's code viewer. No email is sent. Mail clients may strip `<picture>`, block remote images or transform colors; test delivery and appearance in your target clients separately.
348
+
335
349
  ## Sending Rendered HTML as Email
336
350
 
337
351
  Rendering and sending are separate steps. Render with `GenerateHtml`, then hand the string to the standard AntelopeJS email interface, which needs an implementing module (such as `@antelopejs/nodemailer`) loaded in your project configuration.
@@ -39,6 +39,56 @@ What the backend passes to each template. A template that replaces a built-in on
39
39
 
40
40
  `userName` is the user's name, or their email address when they have none. `expiresInHours` is the lifetime as a number, for the template to word in the recipient's language; `expiresIn` is the same lifetime already worded in English (`24 hours`), kept for templates written before `expiresInHours`.
41
41
 
42
+ ## Rendered examples
43
+
44
+ These captures show HTML returned by the DMS frontend's real render endpoint, opened directly in Chromium, rather than the playground's HTML code viewer. All names, codes, addresses and links are fictitious; no email is sent. The default SVG logo is intentionally omitted by `publicLogoOnly`.
45
+
46
+ ### EmailUserValidation
47
+
48
+ The signup email asks Alex to verify their address with a six-digit code, valid for 24 hours.
49
+
50
+ ![Rendered signup verification email with a six-digit code and a 24-hour expiry](../images/emails-user-validation.png)
51
+
52
+ ### EmailResetPassword
53
+
54
+ The password-reset email uses a separate code and the configured recovery lifetime: two hours in this example.
55
+
56
+ ![Rendered password-reset email with a two-hour code expiry](../images/emails-reset-password.png)
57
+
58
+ ### EmailTwoFactor
59
+
60
+ The second-factor email provides a sign-in code. Its footer tells the recipient to ignore the message if they did not attempt to sign in.
61
+
62
+ ![Rendered email second-factor verification code](../images/emails-two-factor.png)
63
+
64
+ ### EmailAdminInvite
65
+
66
+ The invitation names the inviter, workspace and platform when they are known. Its button uses a fictitious destination here.
67
+
68
+ ![Rendered invitation from Jordan Lee to Northwind Studio on Acme Dashboard](../images/emails-admin-invite.png)
69
+
70
+ ### EmailChangeVerification
71
+
72
+ The new address receives the code that confirms the sign-in email change.
73
+
74
+ ![Rendered confirmation email naming the new sign-in address and verification code](../images/emails-change-verification.png)
75
+
76
+ ### EmailChangeNotice
77
+
78
+ The current address receives a notice, not the confirmation code. It tells the recipient what to do if they did not request the change.
79
+
80
+ ![Rendered security notice sent to the current sign-in address](../images/emails-change-notice.png)
81
+
82
+ ### EmailExportReady
83
+
84
+ A complete export provides a download button and an expiry. A partial export also names the sources that could not be included.
85
+
86
+ ![Rendered complete data-export email with a download button and expiry](../images/emails-export-ready.png)
87
+
88
+ ![Rendered partial data-export email warning that Activity history is missing](../images/emails-export-partial.png)
89
+
90
+ These are browser previews, not Gmail, Outlook or Apple Mail compatibility tests. See [Email Layout](/docs/interfaces/html-render/introduction#email-layout) for the custom digest, logo variants and the limits of dark-mode support.
91
+
42
92
  ## Language
43
93
 
44
94
  Each email is written in the recipient's language: the account's `language`, or for an invitation the language it was sent in. English and French are supported; any other language falls back to English.
Binary file
package/docs/index.md CHANGED
@@ -19,6 +19,15 @@ The DMS ships one public AntelopeJS interface package, `@antelopejs/interface-dm
19
19
 
20
20
  This section is the contract reference: the exact symbol names, options and return types, grouped by domain. The DMS guide covers the same ground task by task, organised around what you are building.
21
21
 
22
+ If you are learning the DMS, start with the [Quickstart](/docs/getting-started/quickstart) and [Pages & components](/docs/building/pages-and-components). Return here when you need an import path, a signature, or the exact behavior of an option; you do not need to read every domain before building a page.
23
+
24
+ | Task | Guide with examples | Contract reference |
25
+ | --- | --- | --- |
26
+ | Build a page and choose its layout | [Pages & components](/docs/building/pages-and-components) | [Pages and modules](/docs/interfaces/core/pages-and-modules), [Layout](/docs/interfaces/base/layout) |
27
+ | Add workspace settings | [Settings pages](/docs/building/settings-pages) | [Page access](/docs/interfaces/core/pages-and-modules#page-access) |
28
+ | Protect a route and scope its data | [Auth & tenancy](/docs/auth-and-tenancy/overview) | [Authentication](/docs/interfaces/auth/authentication), [Tenancy](/docs/interfaces/core/tenancy) |
29
+ | Extend another module's screen | [Page extensions](/docs/building/pages-and-components#extending-another-modules-page) | [Extend a page](/docs/interfaces/core/pages-and-modules#extend-a-page-from-another-module) |
30
+
22
31
  Separately published extension interfaces have their own [module interface references](/docs/modules#interface-references), including Builder. This page covers `@antelopejs/interface-dms` itself.
23
32
 
24
33
  ## One installed copy
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/interface-dms",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "Public AntelopeJS interfaces of the DMS: pages, components, permissions, tenancy, auth, notifications and HTML rendering.",
5
5
  "keywords": [
6
6
  "antelopejs",
@@ -33,6 +33,7 @@
33
33
  "scripts": {
34
34
  "build": "rimraf dist && tsc",
35
35
  "typecheck": "tsc --noEmit",
36
+ "test": "pnpm run build && node --test tests/*.test.cjs",
36
37
  "lint": "oxlint && oxfmt --check .",
37
38
  "lint:fix": "oxlint --fix --max-warnings=1000000 && oxfmt .",
38
39
  "format": "oxfmt .",