@wildmason/aegis 1.18.0 → 2.1.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/CHANGELOG.md CHANGED
@@ -18,7 +18,126 @@ that 404s.
18
18
 
19
19
  ## [Unreleased]
20
20
 
21
- _No unreleased changes._
21
+ ## [2.1.0] - 2026-09-20
22
+
23
+ Minor: one new component and its inputs. Nothing existing changes, so a consumer
24
+ on `^2.0.0` picks this up on its next install and nothing it already names moves.
25
+
26
+ ### Added
27
+
28
+ - `<wm-slider>` (`WmSlider`) — bounded continuous value selection, exported from
29
+ `@wildmason/aegis/components` with its `WmSliderTick` type. Built on a native
30
+ `<input type="range">`, so pointer drag, touch, arrow keys, `Page Up`/`Page
31
+ Down`, `Home`/`End`, RTL mirroring and the `role="slider"` semantics come from
32
+ the browser. Every value — two-way `[(value)]` binding, form write, or drag —
33
+ is clamped into `min`…`max` and snapped onto the step grid measured from
34
+ `min`, then written back, so the binding, the form control and the thumb never
35
+ disagree. Implements `ControlValueAccessor`. `ticks` draws labelled stops under
36
+ the rail; `showValue` renders a readout, and `formatValue` formats it and sets
37
+ `aria-valuetext`. Host knobs: `--wm-slider-thumb`, `--wm-slider-track`.
38
+ Documented at the `slider` guide page, served by the MCP, and specified in
39
+ `DESIGN_LANGUAGE.md` §7.6a.
40
+ - `WmSlider` `track` input — `'rail'` (default) or `'segmented'`. A segmented
41
+ track breaks the rail into blocks bounded by the resolved tick grid plus the
42
+ ends of the range, sized by their own spans, with the block holding the value
43
+ filling partially. Knob: `--wm-slider-gap` (3px). There is no `'auto'`
44
+ degradation, deliberately.
45
+ - `WmSlider` `ticks` accepts a **number** (a stop every N from `min`), a
46
+ `WmSliderTick[]`, or a `WmSliderTickFn` of `{ min, max, step }`, all exported.
47
+ `WmSliderTick` gains `level?: 'major' | 'minor'` for a second grid density.
48
+ The resolved grid is clamped, sorted and de-duplicated, and it both draws the
49
+ marks and bounds the segments — one source, two renderings, so a block and the
50
+ mark on its edge cannot disagree. A numeric interval that would draw over 1000
51
+ marks draws none rather than truncating or hanging. The grid stays independent
52
+ of `step`, so a 0–100 slider stepping by 1 can carry an 11-mark grid.
53
+ - `WmSlider` `showValue` reserves its column from the widest string the readout
54
+ can hold — the formatted endpoints plus every labelled stop — instead of from
55
+ the current value. The shell is `1fr auto`, so a column that grew with its text
56
+ took width from the rail and gave it back on the next value; with word labels
57
+ the bar moved while the user dragged it. Unlabelled stops are ignored, so a
58
+ 101-mark numeric grid reserves two strings rather than 101.
59
+
60
+ ### Fixed
61
+
62
+ - The MCP server served every guide page with its code samples missing. The
63
+ extractor decoded HTML entities in its `<pre>` pass, before the document-wide
64
+ tag strip, so an escaped sample — every Angular template in the docs — became
65
+ real tags and was deleted as markup. Entities now stay encoded until after the
66
+ strip and are decoded once at the end, with `&amp;` last so a double-escaped
67
+ sequence cannot be decoded twice. `&#123;` and `&#125;` are decoded for the
68
+ first time, so Angular interpolation braces survive. `mcp/` gains its first
69
+ tests: 8 cases under `node --test`, run by `npm test` in that package.
70
+
71
+ ## [2.0.0] - 2026-09-15
72
+
73
+ Major version because two exports go away: the license UI leaves
74
+ `@wildmason/aegis/components`, and `.wm-kbd--on-accent` is removed. A consumer on
75
+ a `^1.x` range does not pick this release up by accident.
76
+
77
+ ### Removed
78
+
79
+ - **BREAKING:** `.wm-kbd--on-accent`, deprecated in 1.17.0 and slated for removal
80
+ here. Use `.wm-kbd--on-fill`, which carries the same declarations. Helm, Mortar,
81
+ Works, Hearth's Workbench and the docs app used neither name at removal.
82
+
83
+ ### Changed
84
+
85
+ - **BREAKING:** the license UI moved to its own entry point,
86
+ `@wildmason/aegis/license`. `WmLicenseService`, `WmLicenseNudge`,
87
+ `WmLicenseDialog`, `LicenseStatus` and `WmDeclaredUseType` are no longer
88
+ exported from `@wildmason/aegis/components`. Change the import path; nothing
89
+ else about them changed.
90
+
91
+ ```ts
92
+ // before
93
+ import { WmLicenseService, WmLicenseNudge } from '@wildmason/aegis/components';
94
+ // after
95
+ import { WmLicenseService, WmLicenseNudge } from '@wildmason/aegis/license';
96
+ ```
97
+
98
+ The reason is Tauri. The license service calls Tauri commands, and while it
99
+ shared one bundle with every other component, every consumer imported
100
+ `@tauri-apps/api/core`. npm installed it as a required peer, and a web app —
101
+ Hearth's Workbench, measured — kept 1,682 bytes of Tauri IPC code it could
102
+ never call, because that package declares no `sideEffects` and esbuild could
103
+ not prove the import droppable. `@wildmason/aegis/components` now imports
104
+ nothing from Tauri.
105
+ - `@tauri-apps/api` is an **optional** peer. Only `@wildmason/aegis/license`
106
+ needs it, and a Tauri app that uses the license UI already depends on it.
107
+ - `package.json` exports `"."` as well as `"./components"`, both pointing at the
108
+ same bundle. The license bundle imports the shared components by package name,
109
+ so `WmModal`, `WmButton` and `WmToastService` stay single classes rather than
110
+ being copied into a second bundle.
111
+
112
+ ### Security
113
+
114
+ - Dev and build dependencies updated to clear `npm audit`: Angular 21.2.23,
115
+ ng-packagr 21.2.7 and the transitive packages under them for the library, and
116
+ Angular 21.2.23, `@angular/build`/`@angular/cli` 21.2.24 and Vitest 4.1.11 for
117
+ the docs app. Both now report 0 vulnerabilities. The published package ships no
118
+ runtime dependencies, so consumers inherited none of these; the peer ranges are
119
+ unchanged.
120
+
121
+ ### Fixed
122
+
123
+ - The Navigation & Tree Patterns guide page was routed in the docs app but
124
+ missing from `mcp/guide-index.json`, so the MCP server never served it. It is
125
+ indexed now.
126
+ - `DESIGN_LANGUAGE.md` said the package has two entry points and listed three.
127
+ It now lists all four, including `@wildmason/aegis/license`, and the Getting
128
+ Started page shows both import paths.
129
+
130
+ ### Added
131
+
132
+ - A fourth `check:docs` rule: every guide page the docs app routes must be in
133
+ `mcp/guide-index.json`, every indexed slug must be routed, and every indexed
134
+ `htmlPath` must exist. The MCP server skips a missing page with a warning
135
+ nobody sees, which is how the Navigation page went unserved.
136
+ - `check:entry-points`, run by `npm run build` after `ng-packagr`. It fails the
137
+ build if the main bundle imports from `@tauri-apps/` or exports a license
138
+ symbol, if the license bundle is incomplete or carries its own copy of a
139
+ shared component, if any `exports` target does not resolve, or if the Tauri
140
+ peer stops being optional.
22
141
 
23
142
  ## [1.18.0] - 2026-09-02
24
143
 
@@ -342,7 +461,9 @@ _No unreleased changes._
342
461
  Releases before 1.8.0 are in `git log`. See also `wiki/products/Aegis - Health.md`
343
462
  for the decisions behind these changes, including the ones decided against.
344
463
 
345
- [Unreleased]: https://github.com/wildmason/aegis/compare/v1.18.0...HEAD
464
+ [Unreleased]: https://github.com/wildmason/aegis/compare/v2.1.0...HEAD
465
+ [2.1.0]: https://github.com/wildmason/aegis/releases/tag/v2.1.0
466
+ [2.0.0]: https://github.com/wildmason/aegis/releases/tag/v2.0.0
346
467
  [1.18.0]: https://github.com/wildmason/aegis/releases/tag/v1.18.0
347
468
  [1.17.0]: https://github.com/wildmason/aegis/releases/tag/v1.17.0
348
469
  [1.16.0]: https://github.com/wildmason/aegis/releases/tag/v1.16.0
@@ -0,0 +1,153 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, signal, computed, Injectable, input, output, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { invoke } from '@tauri-apps/api/core';
4
+ import { WmToastService, WmButton, WmModal } from '@wildmason/aegis';
5
+
6
+ class WmLicenseService {
7
+ toast = inject(WmToastService);
8
+ license = signal(null, ...(ngDevMode ? [{ debugName: "license" }] : /* istanbul ignore next */ []));
9
+ isLicensed = computed(() => this.license()?.valid ?? false, ...(ngDevMode ? [{ debugName: "isLicensed" }] : /* istanbul ignore next */ []));
10
+ orgName = computed(() => this.license()?.org ?? null, ...(ngDevMode ? [{ debugName: "orgName" }] : /* istanbul ignore next */ []));
11
+ async checkLicense() {
12
+ try {
13
+ const status = await invoke('get_license_status');
14
+ this.license.set(status);
15
+ if (!status.valid && status.error) {
16
+ const message = status.error === 'expired'
17
+ ? 'Your license key has expired.'
18
+ : 'Your license key is not valid.';
19
+ this.toast.error(message);
20
+ }
21
+ }
22
+ catch (e) {
23
+ console.error('License check failed:', e);
24
+ this.license.set(null);
25
+ }
26
+ }
27
+ async installKey(jwt) {
28
+ const status = await invoke('install_license', { key: jwt });
29
+ this.license.set(status);
30
+ return status;
31
+ }
32
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmLicenseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
33
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmLicenseService, providedIn: 'root' });
34
+ }
35
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmLicenseService, decorators: [{
36
+ type: Injectable,
37
+ args: [{ providedIn: 'root' }]
38
+ }] });
39
+
40
+ class WmLicenseNudge {
41
+ licenseService = inject(WmLicenseService);
42
+ /**
43
+ * Self-declared use type from the host app's first-run modal. When the
44
+ * user has declared `personal` or `oss` the nudge stays hidden — they've
45
+ * already told us a commercial key isn't relevant. `commercial` (without
46
+ * a key) and `null` (no declaration yet) keep the nudge visible.
47
+ *
48
+ * Typed `string | null` rather than `WmDeclaredUseType | null` because
49
+ * ng-packagr's partial-compilation d.ts emit drops the `null` from a
50
+ * union with a string-literal type, which then forces every consumer to
51
+ * `as` the binding. Runtime narrowing happens in `shouldShow`.
52
+ */
53
+ declaredUseType = input(null, ...(ngDevMode ? [{ debugName: "declaredUseType" }] : /* istanbul ignore next */ []));
54
+ dismissed = signal(false, ...(ngDevMode ? [{ debugName: "dismissed" }] : /* istanbul ignore next */ []));
55
+ enterLicense = output();
56
+ shouldShow = () => {
57
+ if (this.licenseService.isLicensed())
58
+ return false;
59
+ if (this.dismissed())
60
+ return false;
61
+ const ut = this.declaredUseType();
62
+ if (ut === 'personal' || ut === 'oss')
63
+ return false;
64
+ return true;
65
+ };
66
+ dismiss() {
67
+ this.dismissed.set(true);
68
+ }
69
+ onEnterLicense() {
70
+ this.enterLicense.emit();
71
+ }
72
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmLicenseNudge, deps: [], target: i0.ɵɵFactoryTarget.Component });
73
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: WmLicenseNudge, isStandalone: true, selector: "wm-license-nudge", inputs: { declaredUseType: { classPropertyName: "declaredUseType", publicName: "declaredUseType", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { enterLicense: "enterLicense" }, ngImport: i0, template: "@if (shouldShow()) {\n <div class=\"wm-license-nudge\">\n <span class=\"wm-license-nudge__text\">\n Using this at work? Organizations require a commercial license.\n </span>\n <div class=\"wm-license-nudge__actions\">\n <wm-button variant=\"ghost\" size=\"sm\" (click)=\"onEnterLicense()\">Enter License Key</wm-button>\n <wm-button variant=\"ghost\" size=\"sm\" (click)=\"dismiss()\">Dismiss</wm-button>\n </div>\n </div>\n}\n", styles: [".wm-license-nudge{display:flex;align-items:center;justify-content:space-between;gap:var(--wm-space-md, 12px);padding:var(--wm-space-sm, 8px) var(--wm-space-md, 12px);background:var(--wm-bg-subtle, #2a2a2a);border-bottom:1px solid var(--wm-border, #3a3a3a);font-size:var(--wm-font-sm, 13px);color:var(--wm-fg-secondary, #999)}.wm-license-nudge__text{flex:1}.wm-license-nudge__actions{display:flex;gap:var(--wm-space-xs, 4px);flex-shrink:0}\n"], dependencies: [{ kind: "component", type: WmButton, selector: "wm-button", inputs: ["variant", "size", "disabled", "type", "iconOnly", "ariaLabel", "btnTitle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
74
+ }
75
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmLicenseNudge, decorators: [{
76
+ type: Component,
77
+ args: [{ selector: 'wm-license-nudge', standalone: true, imports: [WmButton], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (shouldShow()) {\n <div class=\"wm-license-nudge\">\n <span class=\"wm-license-nudge__text\">\n Using this at work? Organizations require a commercial license.\n </span>\n <div class=\"wm-license-nudge__actions\">\n <wm-button variant=\"ghost\" size=\"sm\" (click)=\"onEnterLicense()\">Enter License Key</wm-button>\n <wm-button variant=\"ghost\" size=\"sm\" (click)=\"dismiss()\">Dismiss</wm-button>\n </div>\n </div>\n}\n", styles: [".wm-license-nudge{display:flex;align-items:center;justify-content:space-between;gap:var(--wm-space-md, 12px);padding:var(--wm-space-sm, 8px) var(--wm-space-md, 12px);background:var(--wm-bg-subtle, #2a2a2a);border-bottom:1px solid var(--wm-border, #3a3a3a);font-size:var(--wm-font-sm, 13px);color:var(--wm-fg-secondary, #999)}.wm-license-nudge__text{flex:1}.wm-license-nudge__actions{display:flex;gap:var(--wm-space-xs, 4px);flex-shrink:0}\n"] }]
78
+ }], propDecorators: { declaredUseType: [{ type: i0.Input, args: [{ isSignal: true, alias: "declaredUseType", required: false }] }], enterLicense: [{ type: i0.Output, args: ["enterLicense"] }] } });
79
+
80
+ class WmLicenseDialog {
81
+ licenseService = inject(WmLicenseService);
82
+ visible = input.required(...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
83
+ dismiss = output();
84
+ keyInput = signal('', ...(ngDevMode ? [{ debugName: "keyInput" }] : /* istanbul ignore next */ []));
85
+ validationResult = signal(null, ...(ngDevMode ? [{ debugName: "validationResult" }] : /* istanbul ignore next */ []));
86
+ validating = signal(false, ...(ngDevMode ? [{ debugName: "validating" }] : /* istanbul ignore next */ []));
87
+ installed = signal(false, ...(ngDevMode ? [{ debugName: "installed" }] : /* istanbul ignore next */ []));
88
+ onDismiss() {
89
+ this.reset();
90
+ this.dismiss.emit();
91
+ }
92
+ onKeyInput(event) {
93
+ const value = event.target.value;
94
+ this.keyInput.set(value);
95
+ this.validationResult.set(null);
96
+ this.installed.set(false);
97
+ }
98
+ async validate() {
99
+ const jwt = this.keyInput().trim();
100
+ if (!jwt)
101
+ return;
102
+ this.validating.set(true);
103
+ try {
104
+ const status = await this.licenseService.installKey(jwt);
105
+ this.validationResult.set(status);
106
+ if (status.valid) {
107
+ this.installed.set(true);
108
+ }
109
+ }
110
+ catch (e) {
111
+ this.validationResult.set({
112
+ valid: false,
113
+ org: null,
114
+ product: '',
115
+ seats: null,
116
+ tier: null,
117
+ expires: null,
118
+ key_id: null,
119
+ error: String(e),
120
+ });
121
+ }
122
+ finally {
123
+ this.validating.set(false);
124
+ }
125
+ }
126
+ reset() {
127
+ this.keyInput.set('');
128
+ this.validationResult.set(null);
129
+ this.validating.set(false);
130
+ this.installed.set(false);
131
+ }
132
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmLicenseDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
133
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: WmLicenseDialog, isStandalone: true, selector: "wm-license-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { dismiss: "dismiss" }, ngImport: i0, template: "<wm-modal [visible]=\"visible()\" (dismiss)=\"onDismiss()\">\n <div class=\"wm-license-dialog\">\n <h2 class=\"wm-license-dialog__title\">Enter License Key</h2>\n\n @if (installed()) {\n <div class=\"wm-license-dialog__success\">\n <p class=\"wm-license-dialog__success-text\">License activated successfully.</p>\n <dl class=\"wm-license-dialog__details\">\n <dt>Organization</dt>\n <dd>{{ validationResult()?.org }}</dd>\n <dt>Seats</dt>\n <dd>{{ validationResult()?.seats }}</dd>\n <dt>Expires</dt>\n <dd>{{ validationResult()?.expires?.slice(0, 10) }}</dd>\n </dl>\n <wm-button variant=\"primary\" size=\"sm\" (click)=\"onDismiss()\">Done</wm-button>\n </div>\n } @else {\n <textarea\n class=\"wm-license-dialog__input\"\n placeholder=\"Paste your license key here...\"\n [value]=\"keyInput()\"\n (input)=\"onKeyInput($event)\"\n rows=\"4\"\n ></textarea>\n\n @if (validationResult(); as result) {\n @if (!result.valid) {\n <p class=\"wm-license-dialog__error\">\n @if (result.error === 'expired') {\n This license key has expired.\n } @else {\n This is not a valid license key for this product.\n }\n </p>\n }\n }\n\n <div class=\"wm-license-dialog__actions\">\n <wm-button\n variant=\"primary\"\n size=\"sm\"\n [disabled]=\"!keyInput().trim() || validating()\"\n (click)=\"validate()\"\n >\n @if (validating()) { Validating... } @else { Activate }\n </wm-button>\n </div>\n }\n </div>\n</wm-modal>\n", styles: [".wm-license-dialog{background:var(--wm-bg-raised, #1e1e1e);border-radius:var(--wm-radius-md, 8px);padding:var(--wm-space-lg, 20px);width:480px;max-width:90vw;display:flex;flex-direction:column;gap:var(--wm-space-md, 12px)}.wm-license-dialog__title{font-size:var(--wm-font-lg, 16px);font-weight:600;color:var(--wm-fg-primary, #fff);margin:0}.wm-license-dialog__input{width:100%;resize:vertical;font-family:var(--wm-font-mono, monospace);font-size:var(--wm-font-sm, 13px);padding:var(--wm-space-sm, 8px);border:1px solid var(--wm-border, #3a3a3a);border-radius:var(--wm-radius-sm, 4px);background:var(--wm-bg-input, #141414);color:var(--wm-fg-primary, #fff);box-sizing:border-box}.wm-license-dialog__input:focus{outline:none;border-color:var(--wm-accent, #4a9eff)}.wm-license-dialog__error{color:var(--wm-fg-error, #f44);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__success{display:flex;flex-direction:column;gap:var(--wm-space-md, 12px)}.wm-license-dialog__success-text{color:var(--wm-fg-success, #4caf50);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__details{display:grid;grid-template-columns:auto 1fr;gap:var(--wm-space-xs, 4px) var(--wm-space-md, 12px);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__details dt{color:var(--wm-fg-secondary, #999)}.wm-license-dialog__details dd{color:var(--wm-fg-primary, #fff);margin:0}.wm-license-dialog__actions{display:flex;justify-content:flex-end;gap:var(--wm-space-sm, 8px)}\n"], dependencies: [{ kind: "component", type: WmModal, selector: "wm-modal", inputs: ["visible", "align"], outputs: ["dismiss"] }, { kind: "component", type: WmButton, selector: "wm-button", inputs: ["variant", "size", "disabled", "type", "iconOnly", "ariaLabel", "btnTitle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
134
+ }
135
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmLicenseDialog, decorators: [{
136
+ type: Component,
137
+ args: [{ selector: 'wm-license-dialog', standalone: true, imports: [WmModal, WmButton], changeDetection: ChangeDetectionStrategy.OnPush, template: "<wm-modal [visible]=\"visible()\" (dismiss)=\"onDismiss()\">\n <div class=\"wm-license-dialog\">\n <h2 class=\"wm-license-dialog__title\">Enter License Key</h2>\n\n @if (installed()) {\n <div class=\"wm-license-dialog__success\">\n <p class=\"wm-license-dialog__success-text\">License activated successfully.</p>\n <dl class=\"wm-license-dialog__details\">\n <dt>Organization</dt>\n <dd>{{ validationResult()?.org }}</dd>\n <dt>Seats</dt>\n <dd>{{ validationResult()?.seats }}</dd>\n <dt>Expires</dt>\n <dd>{{ validationResult()?.expires?.slice(0, 10) }}</dd>\n </dl>\n <wm-button variant=\"primary\" size=\"sm\" (click)=\"onDismiss()\">Done</wm-button>\n </div>\n } @else {\n <textarea\n class=\"wm-license-dialog__input\"\n placeholder=\"Paste your license key here...\"\n [value]=\"keyInput()\"\n (input)=\"onKeyInput($event)\"\n rows=\"4\"\n ></textarea>\n\n @if (validationResult(); as result) {\n @if (!result.valid) {\n <p class=\"wm-license-dialog__error\">\n @if (result.error === 'expired') {\n This license key has expired.\n } @else {\n This is not a valid license key for this product.\n }\n </p>\n }\n }\n\n <div class=\"wm-license-dialog__actions\">\n <wm-button\n variant=\"primary\"\n size=\"sm\"\n [disabled]=\"!keyInput().trim() || validating()\"\n (click)=\"validate()\"\n >\n @if (validating()) { Validating... } @else { Activate }\n </wm-button>\n </div>\n }\n </div>\n</wm-modal>\n", styles: [".wm-license-dialog{background:var(--wm-bg-raised, #1e1e1e);border-radius:var(--wm-radius-md, 8px);padding:var(--wm-space-lg, 20px);width:480px;max-width:90vw;display:flex;flex-direction:column;gap:var(--wm-space-md, 12px)}.wm-license-dialog__title{font-size:var(--wm-font-lg, 16px);font-weight:600;color:var(--wm-fg-primary, #fff);margin:0}.wm-license-dialog__input{width:100%;resize:vertical;font-family:var(--wm-font-mono, monospace);font-size:var(--wm-font-sm, 13px);padding:var(--wm-space-sm, 8px);border:1px solid var(--wm-border, #3a3a3a);border-radius:var(--wm-radius-sm, 4px);background:var(--wm-bg-input, #141414);color:var(--wm-fg-primary, #fff);box-sizing:border-box}.wm-license-dialog__input:focus{outline:none;border-color:var(--wm-accent, #4a9eff)}.wm-license-dialog__error{color:var(--wm-fg-error, #f44);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__success{display:flex;flex-direction:column;gap:var(--wm-space-md, 12px)}.wm-license-dialog__success-text{color:var(--wm-fg-success, #4caf50);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__details{display:grid;grid-template-columns:auto 1fr;gap:var(--wm-space-xs, 4px) var(--wm-space-md, 12px);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__details dt{color:var(--wm-fg-secondary, #999)}.wm-license-dialog__details dd{color:var(--wm-fg-primary, #fff);margin:0}.wm-license-dialog__actions{display:flex;justify-content:flex-end;gap:var(--wm-space-sm, 8px)}\n"] }]
138
+ }], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: true }] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }] } });
139
+
140
+ // @wildmason/aegis/license
141
+ //
142
+ // The license UI for Wildmason's Tauri apps. It is its own entry point because
143
+ // WmLicenseService calls Tauri commands: keeping it out of
144
+ // @wildmason/aegis/components means a web app never imports @tauri-apps/api.
145
+ // Shared components come from the main entry by package name, never by
146
+ // relative path, so they are not copied into this bundle.
147
+
148
+ /**
149
+ * Generated bundle index. Do not edit.
150
+ */
151
+
152
+ export { WmLicenseDialog, WmLicenseNudge, WmLicenseService };
153
+ //# sourceMappingURL=wildmason-aegis-license.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wildmason-aegis-license.mjs","sources":["../../license/license.service.ts","../../license/license-nudge.ts","../../license/license-nudge.html","../../license/license-dialog.ts","../../license/license-dialog.html","../../license/index.ts","../../license/wildmason-aegis-license.ts"],"sourcesContent":["import { Injectable, signal, computed, inject } from '@angular/core';\nimport { invoke } from '@tauri-apps/api/core';\nimport { WmToastService } from '@wildmason/aegis';\nimport type { LicenseStatus } from './license.types';\n\n@Injectable({ providedIn: 'root' })\nexport class WmLicenseService {\n private toast = inject(WmToastService);\n\n readonly license = signal<LicenseStatus | null>(null);\n readonly isLicensed = computed(() => this.license()?.valid ?? false);\n readonly orgName = computed(() => this.license()?.org ?? null);\n\n async checkLicense(): Promise<void> {\n try {\n const status = await invoke<LicenseStatus>('get_license_status');\n this.license.set(status);\n if (!status.valid && status.error) {\n const message = status.error === 'expired'\n ? 'Your license key has expired.'\n : 'Your license key is not valid.';\n this.toast.error(message);\n }\n } catch (e) {\n console.error('License check failed:', e);\n this.license.set(null);\n }\n }\n\n async installKey(jwt: string): Promise<LicenseStatus> {\n const status = await invoke<LicenseStatus>('install_license', { key: jwt });\n this.license.set(status);\n return status;\n }\n}\n","import { Component, ChangeDetectionStrategy, signal, output, input, inject } from '@angular/core';\nimport { WmLicenseService } from './license.service';\nimport { WmButton } from '@wildmason/aegis';\n\n/**\n * One of the values written to `AppConfig.declared_use_type` by the\n * first-run EULA modal in apps that gate on use-type self-attestation\n * (Mortar). Apps without a use-type system (Helm) leave this `null` and\n * the nudge falls back to its original behaviour: visible to anyone\n * without a valid commercial key.\n */\nexport type WmDeclaredUseType = 'personal' | 'oss' | 'commercial';\n\n@Component({\n selector: 'wm-license-nudge',\n standalone: true,\n imports: [WmButton],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './license-nudge.html',\n styleUrl: './license-nudge.css',\n})\nexport class WmLicenseNudge {\n private licenseService = inject(WmLicenseService);\n\n /**\n * Self-declared use type from the host app's first-run modal. When the\n * user has declared `personal` or `oss` the nudge stays hidden — they've\n * already told us a commercial key isn't relevant. `commercial` (without\n * a key) and `null` (no declaration yet) keep the nudge visible.\n *\n * Typed `string | null` rather than `WmDeclaredUseType | null` because\n * ng-packagr's partial-compilation d.ts emit drops the `null` from a\n * union with a string-literal type, which then forces every consumer to\n * `as` the binding. Runtime narrowing happens in `shouldShow`.\n */\n readonly declaredUseType = input<string | null>(null);\n\n dismissed = signal(false);\n enterLicense = output<void>();\n\n protected readonly shouldShow = () => {\n if (this.licenseService.isLicensed()) return false;\n if (this.dismissed()) return false;\n const ut = this.declaredUseType();\n if (ut === 'personal' || ut === 'oss') return false;\n return true;\n };\n\n dismiss(): void {\n this.dismissed.set(true);\n }\n\n onEnterLicense(): void {\n this.enterLicense.emit();\n }\n}\n","@if (shouldShow()) {\n <div class=\"wm-license-nudge\">\n <span class=\"wm-license-nudge__text\">\n Using this at work? Organizations require a commercial license.\n </span>\n <div class=\"wm-license-nudge__actions\">\n <wm-button variant=\"ghost\" size=\"sm\" (click)=\"onEnterLicense()\">Enter License Key</wm-button>\n <wm-button variant=\"ghost\" size=\"sm\" (click)=\"dismiss()\">Dismiss</wm-button>\n </div>\n </div>\n}\n","import { Component, ChangeDetectionStrategy, signal, input, output, inject } from '@angular/core';\nimport { WmButton, WmModal } from '@wildmason/aegis';\nimport { WmLicenseService } from './license.service';\nimport type { LicenseStatus } from './license.types';\n\n@Component({\n selector: 'wm-license-dialog',\n standalone: true,\n imports: [WmModal, WmButton],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './license-dialog.html',\n styleUrl: './license-dialog.css',\n})\nexport class WmLicenseDialog {\n private licenseService = inject(WmLicenseService);\n\n visible = input.required<boolean>();\n dismiss = output<void>();\n\n keyInput = signal('');\n validationResult = signal<LicenseStatus | null>(null);\n validating = signal(false);\n installed = signal(false);\n\n onDismiss(): void {\n this.reset();\n this.dismiss.emit();\n }\n\n onKeyInput(event: Event): void {\n const value = (event.target as HTMLTextAreaElement).value;\n this.keyInput.set(value);\n this.validationResult.set(null);\n this.installed.set(false);\n }\n\n async validate(): Promise<void> {\n const jwt = this.keyInput().trim();\n if (!jwt) return;\n\n this.validating.set(true);\n try {\n const status = await this.licenseService.installKey(jwt);\n this.validationResult.set(status);\n if (status.valid) {\n this.installed.set(true);\n }\n } catch (e) {\n this.validationResult.set({\n valid: false,\n org: null,\n product: '',\n seats: null,\n tier: null,\n expires: null,\n key_id: null,\n error: String(e),\n });\n } finally {\n this.validating.set(false);\n }\n }\n\n private reset(): void {\n this.keyInput.set('');\n this.validationResult.set(null);\n this.validating.set(false);\n this.installed.set(false);\n }\n}\n","<wm-modal [visible]=\"visible()\" (dismiss)=\"onDismiss()\">\n <div class=\"wm-license-dialog\">\n <h2 class=\"wm-license-dialog__title\">Enter License Key</h2>\n\n @if (installed()) {\n <div class=\"wm-license-dialog__success\">\n <p class=\"wm-license-dialog__success-text\">License activated successfully.</p>\n <dl class=\"wm-license-dialog__details\">\n <dt>Organization</dt>\n <dd>{{ validationResult()?.org }}</dd>\n <dt>Seats</dt>\n <dd>{{ validationResult()?.seats }}</dd>\n <dt>Expires</dt>\n <dd>{{ validationResult()?.expires?.slice(0, 10) }}</dd>\n </dl>\n <wm-button variant=\"primary\" size=\"sm\" (click)=\"onDismiss()\">Done</wm-button>\n </div>\n } @else {\n <textarea\n class=\"wm-license-dialog__input\"\n placeholder=\"Paste your license key here...\"\n [value]=\"keyInput()\"\n (input)=\"onKeyInput($event)\"\n rows=\"4\"\n ></textarea>\n\n @if (validationResult(); as result) {\n @if (!result.valid) {\n <p class=\"wm-license-dialog__error\">\n @if (result.error === 'expired') {\n This license key has expired.\n } @else {\n This is not a valid license key for this product.\n }\n </p>\n }\n }\n\n <div class=\"wm-license-dialog__actions\">\n <wm-button\n variant=\"primary\"\n size=\"sm\"\n [disabled]=\"!keyInput().trim() || validating()\"\n (click)=\"validate()\"\n >\n @if (validating()) { Validating... } @else { Activate }\n </wm-button>\n </div>\n }\n </div>\n</wm-modal>\n","// @wildmason/aegis/license\n//\n// The license UI for Wildmason's Tauri apps. It is its own entry point because\n// WmLicenseService calls Tauri commands: keeping it out of\n// @wildmason/aegis/components means a web app never imports @tauri-apps/api.\n// Shared components come from the main entry by package name, never by\n// relative path, so they are not copied into this bundle.\n\nexport { WmLicenseService } from './license.service';\nexport { WmLicenseNudge } from './license-nudge';\nexport type { WmDeclaredUseType } from './license-nudge';\nexport { WmLicenseDialog } from './license-dialog';\nexport type { LicenseStatus } from './license.types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}