@ti-engine/web-framework 1.19.0 → 1.20.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.
Files changed (52) hide show
  1. package/.env +4 -4
  2. package/CHANGELOG.md +384 -353
  3. package/README.md +73 -73
  4. package/bin/build/post-install.js +18 -18
  5. package/bin/localization/web-server-labels.json +27 -27
  6. package/bin/static/.well-known/appspecific/com.chrome.devtools.json +5 -5
  7. package/bin/static/fragments/components/component-notification-bar.html +21 -21
  8. package/bin/static/fragments/components/component-sidebar.html +33 -33
  9. package/bin/static/fragments/components/component-tooltip.html +10 -10
  10. package/bin/static/fragments/components/component-topbar.html +5 -5
  11. package/bin/static/fragments/frame-administration.html +2 -2
  12. package/bin/static/fragments/frame-application.html +18 -18
  13. package/bin/static/fragments/frame-dashboard.html +2 -2
  14. package/bin/static/fragments/frame-login.html +119 -119
  15. package/bin/static/fragments/frame-not-found.html +2 -2
  16. package/bin/static/fragments/frame-profile.html +2 -2
  17. package/bin/static/index.html +22 -22
  18. package/bin/static/scripts/ti-charts.js +1591 -1591
  19. package/bin/static/scripts/ti-framework.css +3194 -3194
  20. package/bin/static/scripts/ti-framework.js +1427 -1427
  21. package/bin/static/scripts/ti-theme-black-glass.css +216 -216
  22. package/bin/static/scripts/ti-theme-daylight.css +87 -87
  23. package/bin/web-app-manager.js +660 -663
  24. package/bin/web-server.js +936 -937
  25. package/bin/web-server.json +48 -48
  26. package/components/admin-config-handlers.js +95 -92
  27. package/components/auth-manager.js +438 -442
  28. package/components/authorization.js +135 -135
  29. package/components/config-change-notifier.js +98 -98
  30. package/components/config-registry.js +257 -260
  31. package/components/config-service.js +363 -360
  32. package/components/config-store.js +244 -246
  33. package/components/definitions.types.js +28 -26
  34. package/components/session-store.js +113 -110
  35. package/components/user.js +134 -132
  36. package/components/web-config-env.js +85 -85
  37. package/components/web-handlers.js +803 -800
  38. package/package.json +139 -67
  39. package/types/bin/web-app-manager.d.ts +194 -0
  40. package/types/bin/web-server.d.ts +373 -0
  41. package/types/components/admin-config-handlers.d.ts +11 -0
  42. package/types/components/auth-manager.d.ts +125 -0
  43. package/types/components/authorization.d.ts +54 -0
  44. package/types/components/config-change-notifier.d.ts +73 -0
  45. package/types/components/config-registry.d.ts +149 -0
  46. package/types/components/config-service.d.ts +218 -0
  47. package/types/components/config-store.d.ts +128 -0
  48. package/types/components/definitions.types.d.ts +31 -0
  49. package/types/components/session-store.d.ts +56 -0
  50. package/types/components/user.d.ts +83 -0
  51. package/types/components/web-config-env.d.ts +17 -0
  52. package/types/components/web-handlers.d.ts +23 -0
package/CHANGELOG.md CHANGED
@@ -1,353 +1,384 @@
1
- # ti-engine web-framework changelog
2
-
3
- This document will contain the list of changes made to the framework. The format is based on the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
4
-
5
- ## Version 1.19.0
6
-
7
- `/static` was served with `max-age=1y, immutable` for every consumer of the framework. `immutable` is a promise that the bytes behind a URL will never change, and browsers honour it so completely that not even a manual reload revalidates — so the promise is only true for a content-addressed URL (`app.a1b2c3.css`). None of the framework's own assets are named that way (`/static/scripts/ti-framework.js`, the theme sheets), which made this an unsafe default that shipped to npm: a deployed CSS or JS fix would never reach anyone who had already visited, for up to a year, with no way to tell them otherwise. The standalone author's site had worked around it privately by fingerprinting its own asset URLs; every other consumer still inherited the bug.
8
-
9
- * fix(web-server)!: the default `/static` cache policy is now `public, max-age=0, must-revalidate` instead of `max-age=1y, immutable`, so a deployed asset change actually reaches a returning visitor. `express.static` still attaches an `ETag`/`Last-Modified`, so a revalidation of an unchanged asset is answered with a `304` — headers, no body. **A consumer whose asset filenames are content-addressed should opt back in** with `staticCache: { maxAge: 31536000, immutable: true }`; one that appends a content hash to its asset URLs (rather than to the filenames) is equally entitled to it
10
- * feat(web-server): add the `staticCache` configuration block — `maxAge` (whole **seconds**, mapping 1:1 onto the `Cache-Control` directive; an express-style `"1y"` duration string is reported rather than silently reinterpreted as milliseconds), `immutable`, and `immutablePaths` (path prefixes served long-lived and `immutable` regardless of the other two). `immutable` combined with a `maxAge` of 0 is a contradiction and is dropped with a warning, so a half-configured deployment costs a revalidation rather than a year of unreachable assets
11
- * feat(web-server): `staticCache.immutablePaths` defaults to `[ "/fonts/" ]` — a released `.woff2` is an artifact rather than something edited in place, and its filename already carries the family, weight and style. Configurable, and clearable with an explicitly empty array, because that is a statement about how a given deployment manages its font files
12
- * feat(web-framework): add the `TI_WEB_STATIC_MAX_AGE`, `TI_WEB_STATIC_IMMUTABLE`, and `TI_WEB_STATIC_IMMUTABLE_PATHS` environment overrides, so the cache policy is settable per deployment like every other web setting; `TI_WEB_STATIC_IMMUTABLE_PATHS` **replaces** the array, and an explicitly empty value means no long-lived paths
13
- * refactor(web-server): the `/static` mounts write `Cache-Control` per file through `express.static`'s `setHeaders` rather than its `maxAge`/`immutable` options, since the policy is no longer uniform across the tree; the decision itself lives in the pure, unit-tested `TiWebServer.resolveStaticCachePolicy()` (config → policy + warnings, which the caller logs) and `TiWebServer.staticCacheControlFor()` (policy + file → header). The `staticCache` defaults deliberately live on the class rather than in `web-server.json`, because the constructor's `_.merge` merges arrays by index and a consumer's empty `immutablePaths` could otherwise never clear a default entry
14
- * docs(web-framework): document the `staticCache` block, the revalidating default and the reasoning behind it, and the fingerprinting opt-in in the README
15
- * build(release): bump package version from `1.18.1` to `1.19.0`
16
-
17
- ## Version 1.18.1
18
-
19
- Enabling Azure SSO took the instance down at startup: the OAuth2 callback was registered by handing the configured callback value straight to Express as a route path, and the installation docs tell operators to set that value to the full absolute URL registered with the identity provider. Express 5 parses route patterns with path-to-regexp v8, where `:` opens a parameter nameso `https://host/login/azure-callback` throws `Missing parameter name at index 6` and the web server never starts. Google was affected identically, which also made this a prerequisite for the competence Cloud Run deployment, whose `deploy.sh` patches in an absolute callback URL (CA-97).
20
-
21
- * fix(web-framework): register an OAuth2 callback by its **path** rather than by the configured value verbatim, so a callback given as the absolute URL registered with the provider no longer crashes startup; a callback that yields no usable path now logs a WARNING and skips that provider's endpoint instead of taking the instance down, matching how an enabled-but-unconfigured provider is already handled
22
- * feat(web-framework): add `AuthManager.getOAuth2CallbackPath( authMethod )` and the pure, unit-tested `AuthManager.toCallbackPath( callbackUrl )` — reduces an absolute, protocol-relative, path or bare relative callback to its route path (query string and fragment stripped), or `null` when no usable path can be derived. The `redirect_uri` sent to the provider is deliberately left as configured, so an absolute callback keeps matching the provider registration exactly instead of depending on the forwarded protocol/host being correct
23
- * docs(web-framework): document the OpenID Connect provider variables in the README, and state in the README, the competence `INSTALL.md` and `.env.example` that a callback URL may be given as either the absolute registered URL or a path — including what each implies for the `redirect_uri`, and that the path must be the one the app actually receives when a proxy strips a prefix
24
- * build(release): bump package version from `1.18.0` to `1.18.1`
25
-
26
- ## Version 1.18.0
27
-
28
- Every web-server setting a container deployment needs could be supplied per environment except one: the admin allowlist. `auth.admins` was readable only from the config file baked into the image, so a containerized deployment had no way to name an administrator — leaving the admin configuration screens unreachable, or forcing a real identity to be committed to the repository. This closes that gap in the existing `TI_WEB_*` override set (CA-94).
29
-
30
- * feat(web-framework): add the `TI_WEB_AUTH_ADMINS` environment override — comma-separated, **replaces** `auth.admins` (matched against the session user's user ID, username or email), so the admin allowlist is configurable per environment like every other web setting; an explicitly empty value means no admins
31
- * docs(web-framework): document `TI_WEB_AUTH_METHODS` and `TI_WEB_TRUSTED_ORIGINS` in the README's environment-variable list, which had never listed them
32
- * build(release): bump package version from `1.17.1` to `1.18.0`
33
-
34
- ## Version 1.17.1
35
-
36
- A validator that needs to compare its own config document against its previously committed state had no way to do so: `applyEdits`'s cross-document context resolves `getConfig` to the *pending* value for any document inside the current edit batch — by design, so a validator can check a sibling document's post-edit state — but a document is always part of its own edit batch, so calling `getConfig` on itself just hands back the same incoming value already passed as the validator's argument, never its prior state. This silently defeated the competence `research-consent` config's version-bump guard (CA-93).
37
-
38
- * feat(web-framework): add `getStoredConfig(key)` to the `applyEdits` validator context (`ConfigService`) always resolves the current *committed* value from the store, even for the document currently under validation, so a validator comparing its own document against its previous state has a way to do it; purely additive `getConfig`'s existing cross-document (pending-value) semantics are unchanged
39
- * build(release): bump package version from `1.17.0` to `1.17.1`
40
-
41
- ## Version 1.17.0
42
-
43
- Route-registration seams so an application subclass can add its own Express routes and unprotected-route patterns enabling public, content-driven sites (the first consumer being the standalone author's site) to layer a catch-all content resolver over the framework without reaching into private state.
44
-
45
- * feat(web-server): add `TiWebServer.registerRoute( method, path, ...handlers )` — registers a custom route on the underlying Express app from a `defineWebApplicationRoutes()` override (after `super()`), so a catch-all resolver can be mounted after the framework's own routes but before its `*splat` 404 handler. Limited to route-scoped verbs (get/post/put/patch/delete/options/head/all); raises `E_GEN_INVALID_ARGUMENT_TYPE` for any other method and `E_GEN_NOT_INITIALIZED` if called before the Express app exists. The method must be a **string** — a non-string is rejected outright rather than coerced, so a value whose `toString()` happens to yield a verb (`[ "get" ]`, `new String( "get" )`) cannot slip past the allowlist and register a route
46
- * feat(web-server): add `TiWebServer.addUnprotectedRoute( pattern )` — appends a string (exact-match) or RegExp (tested) pattern to the unprotected-routes list from a `defineUnprotectedRoutes()` override, so a public-by-default site can invert the framework's protect-by-default stance; non-string/non-RegExp values are ignored with a warning
47
- * refactor(web-server): extract the unprotected-route matching loop from `isUnprotectedRoute()` into the pure, unit-tested `isRouteInList()` helper (behavior unchanged, including the defensive `lastIndex` reset), and add the `normalizeRegistrableMethod()` helper — both exported for testing alongside the existing `RE_*` matcher constants
48
- * build(release): bump package version from `1.16.0` to `1.17.0`
49
-
50
- ## Version 1.16.0
51
-
52
- Support explicitly trusted request origins so state-changing requests (e.g. login) work behind proxies that do not present the app's external host most notably GitHub Codespaces port forwarding (CA-90).
53
-
54
- * feat(web-framework): add `TI_WEB_TRUSTED_ORIGINS` (comma-separated) / `config.trustedOrigins`. The `originRefererValidationHandler` now accepts a non-GET request whose `Origin`/`Referer` matches the server-reconstructed base URL **or** any configured trusted origin. Previously such a request behind a proxy that rewrote/omitted the forwarded host was rejected with `E_WEB_INVALID_REQUEST_PARAMETERS` (HTTP 403). Backward compatible (empty list = prior behavior); the CSRF double-submit token check is unchanged and still enforced
55
- * build(release): bump package version from `1.15.0` to `1.16.0`
56
-
57
- ## Version 1.15.0
58
-
59
- A dedicated health endpoint, a `TI_WEB_AUTH_METHODS` env override, and login-page gating for every auth methodcompleting the container-friendly auth/health story for the competence deployment (CA-90).
60
-
61
- * feat(web-framework): add an unprotected `GET /health` endpoint (`healthHandler`) that returns `200` with `{ status, broker, uptime }` a purpose-built liveness/readiness probe for container and orchestrator health checks, so probes no longer have to hit the user-facing login route; `broker` reports the Redis connection state
62
- * feat(web-framework): add the `TI_WEB_AUTH_METHODS` env override (comma-separated) which REPLACES `auth.enabledMethods` a clean, 12-factor way to select enabled auth methods per deployment (the config-file merge is by-index and cannot cleanly override an array)
63
- * feat(web-framework): extend login-page auth gating from the OAuth buttons to every method — the `local` credentials form and the "or continue with" divider are now gated too, and a "no method configured" fallback is shown when nothing is enabled, so an SSO-only deployment presents no dead local form
64
- * build(release): bump package version from `1.14.1` to `1.15.0`
65
-
66
- ## Version 1.14.1
67
-
68
- Security hardening for the web-server CodeQL findings raised after the scanner was modernized in CA-90 (CA-91).
69
-
70
- * fix(web-framework): rewrite the default unprotected static-asset route matchers from the ambiguous `(?:.+/)*` to segment-anchored `(?:[^/]+/)*` — the previous form backtracked exponentially and is evaluated against the raw request path in `isUnprotectedRoute()` before authentication, making it a pre-authentication denial-of-service vector (CodeQL js/redos); the matched language for realistic asset paths is unchanged, and the matchers are now the `RE_STATIC_UNPROTECTED` / `RE_WELL_KNOWN_UNPROTECTED` module constants
71
- * fix(web-framework): replace the login-fragment section stripper's `open[\s\S]*?close` global-regex removal with a linear `indexOf`-based `stripMarkerSpans()` helper (also used for the per-provider stripper), eliminating the polynomial-time rescan on hostile input (CodeQL js/polynomial-redos)
72
- * docs(web-framework): document that Helmet's built-in Content-Security-Policy is intentionally disabled because a per-request, nonce-based CSP is enforced by `cspHeaderHandler()` on the following middleware — added an explanatory comment and an inline CodeQL suppression (the alert is a false positive)
73
- * build(release): bump package version from `1.14.0` to `1.14.1`
74
-
75
- ## Version 1.14.0
76
-
77
- `TI_WEB_*` environment-variable overrides for the web server configuration, enabling 12-factor container deployments without per-environment config files (CA-90).
78
-
79
- * feat(web-framework): add `applyWebConfigEnvOverrides( config, env = process.env )` (`#web-config-env`) — a pure helper applying `TI_WEB_HOST`, `TI_WEB_PORT`, `TI_WEB_USE_TLS`, `TI_WEB_TLS_CERT_PATH`, `TI_WEB_TLS_KEY_PATH`, and `TI_WEB_COOKIE_SECRET` overrides onto the merged `TiWebServer` configuration, only when each variable is defined (fully backward compatible)
80
- * fix(web-framework): skip an enabled OpenID Connect provider that has no client ID instead of crashing the instance during discovery — an OAuth-less deployment (e.g. a container started without OAuth credentials) now boots on its remaining methods, with a warning, and reports the dropped provider as unavailable so a sign-in attempt against it fails per-request rather than at startup
81
- * feat(web-framework): the login page now renders an OpenID provider button only when that provider is an effective enabled auth method — the web server passes the post-drop enabled methods to the app manager, which strips the Google/Azure button (and the whole "or continue with" section when no provider is available) from `frame-login.html` at render time
82
- * build(release): bump package version from `1.13.2` to `1.14.0`
83
-
84
- ## Version 1.13.0
85
-
86
- A reusable role-based screen gate and a per-screen title override (back the competence screen-access work and the evaluation/scores screen split).
87
-
88
- * feat(web-framework): the default `TiWebAppManager.verifyAccess` now enforces a fragment's declared `roles` — a fragment registered (via `addFragment`) with a `roles` array is served only to sessions holding at least one of them (otherwise rejected `E_SEC_UNAUTHORIZED_ACCESS` 403), while a fragment with no `roles` stays public. This makes role-restricted screens unreachable by direct URL, not merely hidden in the UI; apps just declare `roles` on `addFragment` — no `verifyAccess` override needed. Backward compatible: all existing role-less fragments remain public
89
- * feat(web-framework): add `authorization.isAccessAllowed( requiredRoles, userRoles )` — a pure, unit-tested access decision (empty/absent roles = public; otherwise ≥1 overlap; no implicit hierarchy, so an `admin` gate is never satisfied by a numeric role) that backs the default `verifyAccess`
90
- * feat(web-framework): add `tiApplication.setScreenTitle( title )` a per-screen topbar/document-title override (cleared automatically on navigation) so a screen can correct its own title at runtime (e.g. a manager viewing another user's scores must not read "My …")
91
- * build(release): bump package version from `1.12.0` to `1.13.0`
92
-
93
- ## Version 1.12.0
94
-
95
- Chart primitives gain legends + value labels for grouped bars and a legend for radar (backs the leaner competence evaluation results view) (CA-61).
96
-
97
- * feat(web-framework): `ti-charts` grouped bars now render an optional swatch legend (`options.legend`) and per-bar value captions (`options.valueLabels`); radar charts render an optional legend, with a dashed swatch variant (`{ dashed: true }`) for dashed series such as an "expected" curve (CA-61)
98
- * feat(web-framework): add `.ti-chart-bar-seg.tone-info`, `.ti-chart-legend-swatch.tone-info`, and `.ti-chart-legend-swatch.is-dashed` so grouped/radar source series and their legends share one colour scale across both themes (CA-61)
99
- * feat(web-framework): `ti-charts` radar accepts an optional per-axis `tone`, applied as a `tone-*` class on the axis label so consumers can colour axis labels (e.g. by category) — threaded through `radarLayout` (CA-61)
100
- * feat(web-framework): `ti-charts` grouped bars accept optional `options.barThickness` (bar height) and `options.valueFontSize` (value-caption font); value captions carry a dedicated `ti-chart-bar-value` class that intentionally sets no CSS `font-size`, so the renderer's `font-size` presentation attribute (default 4) actually governs — previously the caption also carried `ti-chart-bar-label`, whose CSS `font-size: 4px` overrode the attribute and made `valueFontSize` a no-op (CA-61)
101
- * build(release): bump package version from `1.11.1` to `1.12.0`
102
-
103
- ## Version 1.11.1
104
-
105
- Post-review fix from the CA-72 CodeRabbit review (PR #85) on the login test-user panel.
106
-
107
- * fix(web-framework): turning the "override roles (dev)" toggle OFF now always strips any persisted roles from the `ti-test-user` cookie — even when the selected employee is no longer in the panel's profile list — so a stale roles array can't keep overriding the app's org-derived roles on the next login (CA-72)
108
- * build(release): bump package version from `1.11.0` to `1.11.1`
109
-
110
- ## Version 1.11.0
111
-
112
- Login test-user panel defaults to identity-only injection so the app derives roles itself (CA-72).
113
-
114
- * feat(web-framework): login test-user panel injects identity only by default (roles derived by the app); role injection becomes an opt-in dev override (CA-72)
115
- * docs(web-framework): clarify the augmentSession contract (derive vs. override) (CA-72)
116
- * build(release): bump package version from `1.10.4` to `1.11.0`
117
-
118
- ## Version 1.10.4
119
-
120
- Login test-user fixture follow-up (CA-71).
121
-
122
- * chore(web-framework): login test-user `8` carries the `MANAGER` role (`[1, 2]`) so manager / self-manage scenarios (e.g. the competence Org Chart self-manage gate) can be exercised without re-seeding
123
- * build(release): bump package version from `1.10.3` to `1.10.4`
124
-
125
- ## Version 1.10.3
126
-
127
- Developer-tooling and formatting touch-ups on the shared frontend bundle.
128
-
129
- * chore(web-framework): the login test-user panel offers more seeded employees (3, 8, 9 alongside 22/20/1/4) so manager / direct-report / skip-level scenarios can be exercised without re-seeding
130
- * style(web-framework): minor formatting of `formatException`'s return object
131
- * build(release): bump package version from `1.10.2` to `1.10.3`
132
-
133
- ## Version 1.10.2
134
-
135
- Readability and scaling fixes for the chart primitives, surfaced while polishing the Statistics & Results screens (CA-61).
136
-
137
- * fix(web-framework): stacked bar charts now caption each row (the group/cycle label plus an optional per-row value) and render an optional swatch legend driven by `spec.options.legend`, so a coverage "By group" chart reads as labelled bars instead of anonymous colour blocks; the row labels also land on the cross-cycle trend bars
138
- * fix(css): horizontal bar charts opt out of the global `svg` `max-height` so bar thickness and label size stay identical regardless of row counta tall org-wide chart is no longer uniformly scaled down and rendered finer than the same chart on a smaller subtree; the per-row geometry is trimmed for a cleaner look
139
- * feat(css): `.ti-chart-legend` / `.ti-chart-legend-item` / `.ti-chart-legend-swatch` a chart swatch legend whose colours route through the inherited grade/ink chart tokens
140
- * build(release): bump package version from `1.10.1` to `1.10.2`
141
-
142
- ## Version 1.10.1
143
-
144
- Review fixes for the ti-chart primitives (Statistics & Results, CA-61, PR #83 — CodeRabbit pass).
145
-
146
- * fix(web-framework): drillable heatmap/box marks get an accessible name; `renderChart` clears stale `data-ti-chart-empty`/`aria-label` on rerender; `renderStat` renders a missing value as an em dash; the provisional line-dot stroke follows its `tone-*` class
147
- * fix(css): `.ti-chart-sr` uses `clip-path: inset(50%)` instead of the deprecated `clip` property
148
- * build(release): bump package version from `1.10.0` to `1.10.1`
149
-
150
- ## Version 1.10.0
151
-
152
- ### Charting primitive library (Statistics & Results, CA-61)
153
-
154
- * feat(web-framework): new `ti-charts.js` a CSP-safe SVG charting library backing the competence Statistics & Results reporting. Eight primitives via a single `renderChart(figure, spec)` dispatcher: `gauge`, `bars` (stacked / grouped / diverging modes), `stat`, `scatter`, `heatmap` (sequential / diverging scales), `box`, `radar`, and `line` (mean + p25–p75 band, sparkline, stacked, dashed-provisional trailing segment). The pure layout helpers (`gaugeArcPath`, `barSegments`, `scatterLayout`, `heatmapLayout`, `boxLayout`, `radarLayout`, `lineLayout`, …) are unit-tested in isolation
155
- * feat(web-framework): register the `x-ti-chart` Alpine CSP directive (binds a spec object to a host `<figure>`); every chart builds its SVG with `createElementNS` + `setAttribute` only (never `element.style.*` except `setProperty("--var")`) and ships a visually-hidden `.ti-chart-sr` accessibility table
156
- * feat(css): `.ti-chart-*` styles + per-type `figure[data-ti-chart-type]` size caps + `--chart-seq-1…5` sequential ramp tokens and grade/tone colours in both themes (daylight + black-glass)
157
- * build(release): bump package version from `1.9.3` to `1.10.0`
158
-
159
- ## Version 1.9.3
160
-
161
- * feat(css): an empty `.ti-grade-chip` (a competency whose rating is still awaited) now renders an hourglass glyph via `::before` instead of a literal dash, so "awaiting rating" reads as a clear visual state wherever an empty grade chip is shown to a permitted viewer
162
- * build(release): bump package version from `1.9.2` to `1.9.3`
163
-
164
- ## Version 1.9.2
165
-
166
- * feat(css): add `.ti-spacer` a flexible spacer (`flex: 1 1 auto`) that pushes following siblings to the far end of a flex row/column. Promotes the bespoke per-screen `competence-empmgmt-actions-spacer` into a shared primitive, now consumed by the employee-management actions panel and the evaluation screen's team-feedback finalize bar
167
- * build(release): bump package version from `1.9.1` to `1.9.2`
168
-
169
- ## Version 1.9.1
170
-
171
- * build(deps): upgrade `ajv` from ^6.15.0 to ^8.20.0 — ajv 8 renamed the validation-error `dataPath` (dot style) to `instancePath` (JSON Pointer)
172
- * fix(config-registry): normalize ajv 8's `instancePath` back to the dot/bracket data path the registry has always exposed on schema issues (e.g. `.competencies.E1-1.name`, array indices as `[0]`), so the public `ConfigValidationIssue.path` contract is unchanged across the upgrade; the ajv compile options (`meta`, `schemaId: "$id"`, `validateSchema: false`) and Draft-07 handling are unchanged
173
- * build(deps): update `helmet` from ^8.1.0 to ^8.2.0
174
-
175
- ## Version 1.9.0
176
-
177
- * feat(css): add `.ti-panel-body-intro` the canonical description/intro line under a `.ti-panel-head` (`--fs-sm`, secondary foreground, `0 var(--s-3) var(--s-5)` padding, 1.5 line-height); replaces the per-screen intro paragraphs that screens used to hand-style
178
- * refactor(css): tighten the key/value primitives — `.ti-kv-label` is now an uppercase `--fs-xs` 600-weight caption (0.05em letter-spacing); `.ti-kv-value` is `--fs-sm` 400-weight for a consistent, scannable key/value rhythm across screens
179
- * refactor(css): drop the redundant `margin-left: auto` from `.ti-panel-head-aside` (the panel head already positions it via its flex layout)
180
- * fix(sidebar): the user-profile flyout actions (Profile, Settings, Logout) now actually fire. The menu items bind their `hx-*` attributes through Alpine (`x-bind`), which HTMX does not pick up on its initial document scan — so the buttons previously only closed the flyout. The flyout now runs `htmx.process` on its panel when it opens (idempotent on re-open), wiring up each button's `hx-get`/`hx-post`/`hx-target`/`hx-swap`
181
- * fix(sidebar): the role/department line under the user name no longer overflows the fixed-width sidebar — `.ti-sidebar-user-name` and `.ti-sidebar-user-sub` truncate with an ellipsis, and `.ti-sidebar-user-text` gets `flex: 1` so it bounds the text column
182
- * style(css): expand the `.ti-icon` size-modifier one-liners (`.xs`/`.sm`/`.md`/`.lg`/`.xl`) to block form for consistency with the rest of the sheet
183
- * build(release): bump package version from `1.8.0` to `1.9.0`
184
-
185
- ## Version 1.8.0
186
-
187
- * feat(notifications): notifications can now show a secondary **details** line under the generic message. `tiApplication.formatException` returns `{ message, details }` (resolved from the exception's `data.details`, falling back to the raw text for non-localized messages) and `tiApplication.notify` accepts that payload so an error like "The request parameters are not recognized or not supported." now also shows the specifics (e.g. "Competency codes not in the 'QE' pool: …") in a smaller, muted font. The returned object stringifies to its message, so existing string usages keep working unchanged
188
- * fix(css): raise the toast stack above the modal layer (`z-index` 1100 → 1300; the modal backdrop is 1200) so a notification raised while a modal is open is no longer hidden behind it
189
-
190
- ## Version 1.7.1
191
-
192
- * fix(web-handlers): web-application request errors that carry no explicit `httpCode` are no longer reported as `500`. A new `resolveHttpCode` derives the status from the exception code — request-validation and application-logic errors (`E_WEB_*` / `E_APP_*`) map to `422 Unprocessable Content`, security (`E_SEC_*`) to `403`, resource not-found/already-exists to `404`/`409`, and method/URI/content errors to `405`/`404`/`415`; only genuine internal, communication, and unknown errors still default to `500`. An explicit `httpCode` on the exception always wins. Applied in both the `/app` request handler (`formatException`) and the default error handler
193
-
194
- ## Version 1.7.0
195
-
196
- * feat(css): introduce `.ti-data-grid` family — `.ti-data-grid`, `.ti-data-grid-head`, `.ti-data-grid-rows`, `.ti-data-grid-row` with shared `--ti-grid-cols` template; row state modifiers `.is-current` (accent-soft, "current user") and `.is-selected` (accent-soft + left accent bar); wrapper variants `.bordered` (horizontal dividers for tabular displays) and `.compact` (denser padding); cell utilities `.ti-cell-center` / `.ti-cell-right` for per-cell alignment
197
- * feat(css): introduce `.ti-page-head` as a vertical block stack (eyebrow above title, subtitle below) using `--fs-xs` for the eyebrow and clamping subtitle width at 60ch
198
- * feat(css): introduce a reusable `.ti-form*` family — `.ti-form`, `.ti-form-section`, `.ti-form-section-title`, `.ti-form-grid` (with `.cols-1` / `.cols-3` modifiers), `.ti-form-row` (with `.wide` for grid-spanning), `.ti-form-readonly`, `.ti-form-hint`, `.ti-form-error`, `.ti-form-actions`, `.ti-form-state` (with `.saved` / `.unsaved`); single responsive collapse to one column under 720px
199
- * feat(css): extend `.ti-panel-head` with sub-elements — `.ti-panel-head-icon` (32x32 framed icon slot), `.ti-panel-head-text` (title + subtitle stack inside a flex row), `.ti-panel-title-aside` (inline qualifier next to the title), `.ti-panel-subtitle` (dimmed sub line), `.ti-panel-head-aside` (right-aligned read-only info with left-border separator), and the `.bar` modifier (sunken full-width banner)
200
- * feat(icons)!: rebase `.ti-icon` on `background-color: currentColor` so icons inherit the surrounding text colour; add size modifiers `.xs` (12px), `.sm` (14px), `.md` (16px), `.lg` (24px), `.xl` (32px); add `.legacy-gray` modifier to preserve the previous gray/hover behaviour for consumers that rely on the fixed colour scheme
201
- * feat(icons): add 21 new `.ti-icon` variants (lucide / feather style, 24x24 viewBox) — `plus`, `close`, `check`, `check-clipboard`, `send`, `search`, `clock`, `warning-triangle`, `info-circle`, `bell`, `check-circle`, `eye`, `calendar-blank`, `user`, `users`, `briefcase`, `folder`, `book`, `help-circle`, `bar-chart`, `chevron-left`, `chevron-right`, `dashboard-grid`, `cycles-loop`, `sun`
202
- * feat(icons): add `.ti-icon.moon` mask variant so theme toggles can mirror the target mode
203
- * feat(framework): add `tiApplication.hasRole(roleCode)` helper that does the array-shape check in plain JS (the Alpine CSP build does not expose `Array` to its expression evaluator, so `Array.isArray(...)` written inline in a template raises `Undefined variable: Array`)
204
- * feat(framework): add `tiApplication.topbarPrimaryCta` store slot plus `setTopbarPrimaryCta` / `setTopbarPrimaryCtaDisabled` API for per-screen CTA buttons in the topbar; auto-cleared on screen navigation so each screen owns its slot
205
- * feat(css): native select chevron replaced by a custom down-chevron SVG positioned at right: 10px / 14x14; padding-right reserves the slot; glass theme overrides the SVG stroke colour because `background-image` can't pick up `currentColor`
206
- * feat(css): subdue `::-webkit-calendar-picker-indicator` to opacity 0.7 (1 on hover) so date-input visual weight matches the chevron
207
- * feat(notification bar): replace inline toast SVGs with `.ti-icon` mask classes (success check, danger close, warn triangle, info circle, close button)
208
- * feat(sidebar): replace inline navigation SVGs with `.ti-icon` mask classes (collapse chevron, dashboard home, sun theme toggle)
209
- * refactor(css): drop the screen-specific page-header, form, and tabular-layout CSS that duplicated framework primitives; all in-tree screens (`frame-employees-list`, `frame-cycles`, `frame-cycle-setup`, `frame-competence-evaluation`, `frame-new-evaluation`, `frame-manager-calendar`, `frame-interview-schedule`, `frame-employee-management`) now consume `.ti-page-head`, `.ti-data-grid`, `.ti-form*`, and `.ti-panel-head*` instead
210
- * docs(modal): doc-block on `.ti-modal-*` confirming it as the canonical shared primitive (introduced in 1.6.3 via the competence cycle-setup work)
211
- * build(release): bump package version from `1.6.3` to `1.7.0`
212
-
213
- ## Version 1.6.3
214
-
215
- * feat(css): add `--ti-internal-padding` CSS variable to the design token system
216
- * feat(css): add `--ti-border-color` CSS variable for consistent border theming
217
- * feat(icons): add `.ti-icon.calendar` and `.ti-icon.schedule` icon variants with hover states
218
- * feat(css): add `.ti-data-value.fill-space` modifier for flex-grow behavior in inline data value layouts
219
- * fix(css): remove `min-width: 120px` constraint from `.ti-button.inline` for more flexible sizing
220
- * fix(css): update z-index stacking values for dropdown and overlay elements to prevent layering conflicts
221
- * build(deps): update `openid-client` from ^6.8.2 to ^6.8.4
222
- * build(deps): update bundled `@alpinejs/csp` from ^3.15.11 to ^3.15.12
223
- * build(deps): update bundled `htmx.org` from ^2.0.8 to ^2.0.10
224
- * build(static): refresh bundled Alpine.js CSP and HTMX library files to match updated dependency versions
225
-
226
- ## Version 1.6.2
227
-
228
- * feat(ui): replace Material Symbols usage with framework-native `.ti-icon` classes across sidebar flyouts, login actions, and notification bar
229
- * feat(sidebar): merge administration and user flyout menus into a single `sidebarApplicationMenu` with configurable menu icon and updated actions
230
- * feat(css): add embedded SVG mask icon variants (`app-menu`, `dashboard`, `settings`, `error`, `user-profile`, `login`, `logout`, `internet`) and increase default icon size to `24px`
231
- * refactor(theme): remove Material Symbols-specific icon styling from the black-glass theme
232
- * build(static): remove external Google Material Symbols stylesheet import from static `index.html`
233
- * build(release): bump package version from `1.6.1` to `1.6.2`
234
-
235
- ## Version 1.6.1
236
-
237
- * feat(css): add inline button support via `.ti-button.inline` and new `--ti-button-inline-height` design token
238
- * refactor(ui): update sidebar flyout positioning logic to use shared `tiToolbox` viewport helpers (`getVisibleBox`, `clampToBox`)
239
- * fix(ui): fix the call to utility functions `getVisibleBox` and `clampToBox` in the sidebar flyout component
240
- * build(release): bump package version from `1.6.0` to `1.6.1`
241
-
242
- ## Version 1.6.0
243
-
244
- * feat(toolbox): add Alpine.js `tiToolbox` store with shared utility methods (`deepMerge`, `deepFreeze`, `structuredClone`, `formatDate`, viewport helpers, and cookie access)
245
- * feat(ui): move sidebar menu configuration into `ti-framework.js` and register `tiComponentsConfig` during Alpine.js initialization
246
- * refactor(static): remove legacy `ti-user-interface.js` from static assets and stop loading it from `index.html`
247
- * refactor(components): update framework components to consume toolbox utilities through Alpine stores
248
- * refactor(docs): add and expand JSDoc typedefs and method-level documentation in `ti-framework.js`
249
- * build(release): bump package version from `1.5.3` to `1.6.0`
250
-
251
- ## Version 1.5.3
252
-
253
- * feat(framework): add `openScreen` method for in-app navigation
254
- * fix(auth): add explicit HTTP `401` status to authentication failure
255
-
256
- ## Version 1.5.2
257
-
258
- * feat(framework): expand application API by improving `sendRequest`, `notify`, and `getLabel` methods
259
- * feat(tooltip): add helper methods `getTooltipMessage`, `handleEnter`, `handleLeave`, `showTooltip`, `hideTooltip` to the tooltip component
260
- * feat(css): improve styles and style structure
261
-
262
- ## Version 1.5.1
263
-
264
- * feat(framework): add `isValidDate` utility function for validating Date instances
265
- * feat(framework): add `deepFreeze` utility function for recursive object freezing
266
- * feat(framework): add `getLabel` method on application configuration for nested label resolution with dot notation
267
- * feat(framework): add Alpine.js directive `x-text-label` for runtime label translation
268
- * feat(framework): add a configuration object to replace labels object with enhanced structure
269
- * feat(config): add authentication state (`auth.isAuthenticated`) to config endpoint response
270
- * feat(placeholder): add inner content capture and injection for placeholder replacement
271
- * feat(tooltip): add a new tooltip component with Alpine.js integration and positioning
272
- * feat(css): add CSS custom properties for padding, margin, and font-family
273
- * feat(css): add tooltip styling variables (background, foreground, size, shadow, arrow)
274
- * feat(css): add `.ti-content.pane` block with flex layout and overflow handling
275
- * feat(css): add error color, flyout item shadows, and separator color variables
276
- * feat(css): add `.ti-glass-btn-black.large` variant with left-justified content
277
- * refactor(framework): change user initialization from `undefined` to `null`
278
- * refactor(framework): improve request failure handling with proper error rejection
279
- * refactor(css): replace hard-coded spacing with CSS variables across components
280
- * refactor(css): add `overflow: hidden` to body and `.ti-main` for better layout control
281
- * refactor(css): convert color and styling values to CSS variables throughout
282
- * refactor(handlers): add `convertUriToString` helper for safe URI object stringification
283
- * refactor(handlers): add a request context object (query, params, headers, url, method) to JSON responses
284
- * refactor(handlers): augment user data with default employeeID and roles in the user information handler
285
- * build(deps): update express from ^5.1.0 to ^5.2.1
286
- * build(deps): update express-session from ^1.18.2 to ^1.19.0
287
- * build(deps): update lodash from ^4.17.21 to ^4.17.23
288
- * build(deps): update openid-client from ^6.8.1 to ^6.8.2
289
- * build(deps): update `@alpinejs/csp` from ^3.15.2 to ^3.15.8
290
- * build(engines): update Node.js requirement from >=18.0.0 to >=20.0.0
291
-
292
- ## Version 1.5.0
293
-
294
- * feat(web-app)!: change `TiWebAppManager` to be an abstract class
295
- * feat(web-app): rename class `WebAppManager` to `TiWebAppManager` and add a static file caching mechanism
296
- * feat(web-app): add `addFragment` method with override protection for custom HTML fragment registration
297
- * feat(web-app): add `webAppIdentifier` getter to expose application identifier
298
- * feat(web-server): add support for dynamic web application instantiation from config via `classPath`
299
- * feat(web-server): add `defineWebApplicationRoutes` and `defineUnprotectedRoutes` extension points
300
- * feat(web-server): add `endpointEnabled` flag to conditionally enable API endpoint proxy
301
- * feat(web-server): add serving for `.well-known` directory for web standards compliance
302
- * feat(package): add public exports for `./web-application` and `./web-server` subpaths
303
- * feat(package): add `files` whitelist and repository metadata (homepage, bugs URL, git repository)
304
- * feat(package): add Node.js version requirement (>=18.0.0) via the `engines` field
305
- * feat(build): add a post-install script to vendor HTMX and Alpine.js CSP libraries locally
306
- * refactor(web-app): replace `fullPublicPath` with `staticContentPaths` array for multi-path static content resolution
307
- * refactor(web-app): add file location search algorithm with caching via `#locateStaticFile` method
308
- * refactor(web-app): update `transformHtml` signature to remove `fullPublicPath` parameter
309
- * refactor(web-app): update `assembleHtmlView` to accept `staticContentPaths` instead of `fullPublicPath`
310
- * refactor(web-server): replace the single static path with configurable `staticContentPaths` array
311
- * refactor(web-server): merge web server default config with the provided service config in constructor
312
- * refactor(web-server): load web server default config directly from the package JSON import instead of the ENV configuration
313
- * refactor(web-server): improve TLS initialization error handling to reject promise instead of throw
314
- * refactor(package): reorganize imports from `./server/...` to `./bin/...` and `./components/...` paths
315
- * refactor(package): move `@alpinejs/csp` from dependencies to devDependencies
316
- * build(static): replace CDN script references with local copies for HTMX and Alpine.js CSP
317
- * build(env): remove `TI_INSTANCE_CONFIG` and update `TI_LOCALIZATION_LABELS_PATH` to use `bin/localization/` path
318
- * build(env): add `TI_AUDITING_LOG_MIN_LEVEL` configuration variable
319
- * fix(ui): change `aria-expanded` binding from string to boolean in the sidebar flyout component
320
- * fix(ui): remove incorrect `type="button"` attribute from `Home` anchor element
321
- * docs: improve various documentation comments and class descriptions
322
-
323
- ## Version 1.4.0
324
-
325
- * feat(ui): add a notification bar component with Alpine.js integration and auto-dismiss functionality
326
- * feat(ui): add CSS variables and styles for the notification system
327
- * feat(routes): add `/not-found` fragment and route for 404 error pages
328
- * feat(routes): add `/app/error` route for error handling testing - will be removed later
329
- * feat(routes): add `/app/config` data endpoint for serving application configuration
330
- * feat(localization): add language property to `User` class with getter and JSON serialization
331
- * feat(localization): integrate localization module for label management and localized error messages
332
- * feat(session): add language property to session data populated from user or service configuration
333
- * feat(handlers): add response type detection helper `isAcceptingResponseType` for content negotiation
334
- * feat(handlers): enhance error responses with localized messages via localization module
335
- * feat(handlers): add HTMX-aware error handling with HX-Redirect and HX-Retarget headers
336
- * feat(handlers): implement `processDataRequest` method in WebAppManager for serving data resources
337
- * feat(config): add a language configuration option to the `WebServiceConfiguration` object
338
- * refactor(handlers): delegate error handling from resource protection to error middleware
339
- * refactor(handlers): improve 401/404 response handling by routing through exceptions and middleware
340
- * refactor(handlers): enhance CSRF and origin validation to use error middleware instead of direct responses
341
- * refactor(handlers): improve service call error handling to raise exceptions and delegate to middleware
342
- * refactor(handlers): refactor invalid route handler to raise exceptions instead of direct 404 responses
343
- * fix(types): correct ExpressRequest typedef from `import("express").req` to `import("express").Request`
344
- * build(config): update publicPath from `packages/web-framework/bin/static` to `bin/static`
345
- * build(config): update TLS certificate paths to use relative `bin/tls/` paths
346
- * build(env): update `TI_INSTANCE_CLASS` and `TI_INSTANCE_CONFIG` paths to use relative `bin/` paths
347
- * build(env): add `TI_LOCALIZATION_LABELS_PATH` environment variable for custom labels
348
- * build(run): update IDE run configuration to use relative paths and the correct working directory
349
- * build(labels): add an empty `web-server-labels.json` file for custom localization labels
350
-
351
- ## Version 1.3.0
352
-
353
- * feat: first working prototype version
1
+ # ti-engine web-framework changelog
2
+
3
+ This document will contain the list of changes made to the framework. The format is based on the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
4
+
5
+ ## Version 1.20.0
6
+
7
+ TypeScript declarations now ship with the package, verified against a consumer type-checking with `skipLibCheck: false`
8
+ before they can be committed. See `@ti-engine/core` 1.9.0 for why the previous attempt was withdrawn.
9
+
10
+ * feat(types): generate and publish `.d.ts` declarations for every module, wired through `types` conditions in both
11
+ `exports` and `imports`
12
+ * feat(definitions): expose the shared type definitions as `@ti-engine/web-framework/definitions`
13
+ * fix(session-store)!: the store's `set`, `get` and `touch` are typed against `express-session`'s `SessionData`, which
14
+ is what the `Store` contract hands them not `TiSession`, which additionally requires `id`, `save`, `regenerate` and
15
+ `destroy`. The implementation only ever reads `cookie.maxAge`, so the annotation was describing a stricter input than
16
+ the base class can supply and than the code needs. Documentation only; no behaviour changes
17
+ * fix(web-handlers): `ExpressResponse` referred to `import("express").res`, which express does not export. It is
18
+ `Response`
19
+ * refactor(exports): the extras hung off `module.exports` after a class assignment — `instance`, `authMethod`,
20
+ `applyAuthMethodVisibility`, the static route matchers — are assigned to the class itself. `module.exports` *is* the
21
+ class, so this is the same object with the same properties at runtime; as a declaration it is the difference between
22
+ a namespace merge and an export assignment colliding with named exports, which is not valid TypeScript
23
+ * fix(types): the same closure-style `{function(...)}` and `@private`-on-`#member` corrections as `core` 1.9.0
24
+ * build(deps): add `@types/express`, `@types/express-session` and `@types/node`. The published declarations name types
25
+ from all three
26
+
27
+ ## Version 1.19.1
28
+
29
+ Documentation and packaging only — no functional change.
30
+
31
+ The `SemanticValidator` fix was found while attempting to generate TypeScript declarations from the framework's JSDoc. That work is **deferred to a later release**, but this correction stands on its own: the typedef was written as `function(Object, ValidatorContext): (ConfigValidationIssue[]|Promise<ConfigValidationIssue[]>)`, whose parenthesised return type inside the Closure form no parser can read. It described the validator contract correctly to a human reader and not at all to a machine.
32
+
33
+ * fix(config-registry): rewrite the `SemanticValidator` typedef in arrow syntax
34
+ * feat(package): declare `keywords`, which the package had none of — the terms npm search matches against
35
+
36
+ ## Version 1.19.0
37
+
38
+ `/static` was served with `max-age=1y, immutable` for every consumer of the framework. `immutable` is a promise that the bytes behind a URL will never change, and browsers honour it so completely that not even a manual reload revalidates — so the promise is only true for a content-addressed URL (`app.a1b2c3.css`). None of the framework's own assets are named that way (`/static/scripts/ti-framework.js`, the theme sheets), which made this an unsafe default that shipped to npm: a deployed CSS or JS fix would never reach anyone who had already visited, for up to a year, with no way to tell them otherwise. The standalone author's site had worked around it privately by fingerprinting its own asset URLs; every other consumer still inherited the bug.
39
+
40
+ * fix(web-server)!: the default `/static` cache policy is now `public, max-age=0, must-revalidate` instead of `max-age=1y, immutable`, so a deployed asset change actually reaches a returning visitor. `express.static` still attaches an `ETag`/`Last-Modified`, so a revalidation of an unchanged asset is answered with a `304` — headers, no body. **A consumer whose asset filenames are content-addressed should opt back in** with `staticCache: { maxAge: 31536000, immutable: true }`; one that appends a content hash to its asset URLs (rather than to the filenames) is equally entitled to it
41
+ * feat(web-server): add the `staticCache` configuration block — `maxAge` (whole **seconds**, mapping 1:1 onto the `Cache-Control` directive; an express-style `"1y"` duration string is reported rather than silently reinterpreted as milliseconds), `immutable`, and `immutablePaths` (path prefixes served long-lived and `immutable` regardless of the other two). `immutable` combined with a `maxAge` of 0 is a contradiction and is dropped with a warning, so a half-configured deployment costs a revalidation rather than a year of unreachable assets
42
+ * feat(web-server): `staticCache.immutablePaths` defaults to `[ "/fonts/" ]` — a released `.woff2` is an artifact rather than something edited in place, and its filename already carries the family, weight and style. Configurable, and clearable with an explicitly empty array, because that is a statement about how a given deployment manages its font files
43
+ * feat(web-framework): add the `TI_WEB_STATIC_MAX_AGE`, `TI_WEB_STATIC_IMMUTABLE`, and `TI_WEB_STATIC_IMMUTABLE_PATHS` environment overrides, so the cache policy is settable per deployment like every other web setting; `TI_WEB_STATIC_IMMUTABLE_PATHS` **replaces** the array, and an explicitly empty value means no long-lived paths
44
+ * refactor(web-server): the `/static` mounts write `Cache-Control` per file through `express.static`'s `setHeaders` rather than its `maxAge`/`immutable` options, since the policy is no longer uniform across the tree; the decision itself lives in the pure, unit-tested `TiWebServer.resolveStaticCachePolicy()` (config → policy + warnings, which the caller logs) and `TiWebServer.staticCacheControlFor()` (policy + file → header). The `staticCache` defaults deliberately live on the class rather than in `web-server.json`, because the constructor's `_.merge` merges arrays by index and a consumer's empty `immutablePaths` could otherwise never clear a default entry
45
+ * docs(web-framework): document the `staticCache` block, the revalidating default and the reasoning behind it, and the fingerprinting opt-in in the README
46
+ * build(release): bump package version from `1.18.1` to `1.19.0`
47
+
48
+ ## Version 1.18.1
49
+
50
+ Enabling Azure SSO took the instance down at startup: the OAuth2 callback was registered by handing the configured callback value straight to Express as a route path, and the installation docs tell operators to set that value to the full absolute URL registered with the identity provider. Express 5 parses route patterns with path-to-regexp v8, where `:` opens a parameter name — so `https://host/login/azure-callback` throws `Missing parameter name at index 6` and the web server never starts. Google was affected identically, which also made this a prerequisite for the competence Cloud Run deployment, whose `deploy.sh` patches in an absolute callback URL (CA-97).
51
+
52
+ * fix(web-framework): register an OAuth2 callback by its **path** rather than by the configured value verbatim, so a callback given as the absolute URL registered with the provider no longer crashes startup; a callback that yields no usable path now logs a WARNING and skips that provider's endpoint instead of taking the instance down, matching how an enabled-but-unconfigured provider is already handled
53
+ * feat(web-framework): add `AuthManager.getOAuth2CallbackPath( authMethod )` and the pure, unit-tested `AuthManager.toCallbackPath( callbackUrl )` — reduces an absolute, protocol-relative, path or bare relative callback to its route path (query string and fragment stripped), or `null` when no usable path can be derived. The `redirect_uri` sent to the provider is deliberately left as configured, so an absolute callback keeps matching the provider registration exactly instead of depending on the forwarded protocol/host being correct
54
+ * docs(web-framework): document the OpenID Connect provider variables in the README, and state in the README, the competence `INSTALL.md` and `.env.example` that a callback URL may be given as either the absolute registered URL or a path including what each implies for the `redirect_uri`, and that the path must be the one the app actually receives when a proxy strips a prefix
55
+ * build(release): bump package version from `1.18.0` to `1.18.1`
56
+
57
+ ## Version 1.18.0
58
+
59
+ Every web-server setting a container deployment needs could be supplied per environment except one: the admin allowlist. `auth.admins` was readable only from the config file baked into the image, so a containerized deployment had no way to name an administrator leaving the admin configuration screens unreachable, or forcing a real identity to be committed to the repository. This closes that gap in the existing `TI_WEB_*` override set (CA-94).
60
+
61
+ * feat(web-framework): add the `TI_WEB_AUTH_ADMINS` environment override comma-separated, **replaces** `auth.admins` (matched against the session user's user ID, username or email), so the admin allowlist is configurable per environment like every other web setting; an explicitly empty value means no admins
62
+ * docs(web-framework): document `TI_WEB_AUTH_METHODS` and `TI_WEB_TRUSTED_ORIGINS` in the README's environment-variable list, which had never listed them
63
+ * build(release): bump package version from `1.17.1` to `1.18.0`
64
+
65
+ ## Version 1.17.1
66
+
67
+ A validator that needs to compare its own config document against its previously committed state had no way to do so: `applyEdits`'s cross-document context resolves `getConfig` to the *pending* value for any document inside the current edit batch — by design, so a validator can check a sibling document's post-edit state — but a document is always part of its own edit batch, so calling `getConfig` on itself just hands back the same incoming value already passed as the validator's argument, never its prior state. This silently defeated the competence `research-consent` config's version-bump guard (CA-93).
68
+
69
+ * feat(web-framework): add `getStoredConfig(key)` to the `applyEdits` validator context (`ConfigService`) — always resolves the current *committed* value from the store, even for the document currently under validation, so a validator comparing its own document against its previous state has a way to do it; purely additive — `getConfig`'s existing cross-document (pending-value) semantics are unchanged
70
+ * build(release): bump package version from `1.17.0` to `1.17.1`
71
+
72
+ ## Version 1.17.0
73
+
74
+ Route-registration seams so an application subclass can add its own Express routes and unprotected-route patterns — enabling public, content-driven sites (the first consumer being the standalone author's site) to layer a catch-all content resolver over the framework without reaching into private state.
75
+
76
+ * feat(web-server): add `TiWebServer.registerRoute( method, path, ...handlers )` — registers a custom route on the underlying Express app from a `defineWebApplicationRoutes()` override (after `super()`), so a catch-all resolver can be mounted after the framework's own routes but before its `*splat` 404 handler. Limited to route-scoped verbs (get/post/put/patch/delete/options/head/all); raises `E_GEN_INVALID_ARGUMENT_TYPE` for any other method and `E_GEN_NOT_INITIALIZED` if called before the Express app exists. The method must be a **string** — a non-string is rejected outright rather than coerced, so a value whose `toString()` happens to yield a verb (`[ "get" ]`, `new String( "get" )`) cannot slip past the allowlist and register a route
77
+ * feat(web-server): add `TiWebServer.addUnprotectedRoute( pattern )` — appends a string (exact-match) or RegExp (tested) pattern to the unprotected-routes list from a `defineUnprotectedRoutes()` override, so a public-by-default site can invert the framework's protect-by-default stance; non-string/non-RegExp values are ignored with a warning
78
+ * refactor(web-server): extract the unprotected-route matching loop from `isUnprotectedRoute()` into the pure, unit-tested `isRouteInList()` helper (behavior unchanged, including the defensive `lastIndex` reset), and add the `normalizeRegistrableMethod()` helper — both exported for testing alongside the existing `RE_*` matcher constants
79
+ * build(release): bump package version from `1.16.0` to `1.17.0`
80
+
81
+ ## Version 1.16.0
82
+
83
+ Support explicitly trusted request origins so state-changing requests (e.g. login) work behind proxies that do not present the app's external host — most notably GitHub Codespaces port forwarding (CA-90).
84
+
85
+ * feat(web-framework): add `TI_WEB_TRUSTED_ORIGINS` (comma-separated) / `config.trustedOrigins`. The `originRefererValidationHandler` now accepts a non-GET request whose `Origin`/`Referer` matches the server-reconstructed base URL **or** any configured trusted origin. Previously such a request behind a proxy that rewrote/omitted the forwarded host was rejected with `E_WEB_INVALID_REQUEST_PARAMETERS` (HTTP 403). Backward compatible (empty list = prior behavior); the CSRF double-submit token check is unchanged and still enforced
86
+ * build(release): bump package version from `1.15.0` to `1.16.0`
87
+
88
+ ## Version 1.15.0
89
+
90
+ A dedicated health endpoint, a `TI_WEB_AUTH_METHODS` env override, and login-page gating for every auth method completing the container-friendly auth/health story for the competence deployment (CA-90).
91
+
92
+ * feat(web-framework): add an unprotected `GET /health` endpoint (`healthHandler`) that returns `200` with `{ status, broker, uptime }` — a purpose-built liveness/readiness probe for container and orchestrator health checks, so probes no longer have to hit the user-facing login route; `broker` reports the Redis connection state
93
+ * feat(web-framework): add the `TI_WEB_AUTH_METHODS` env override (comma-separated) which REPLACES `auth.enabledMethods` — a clean, 12-factor way to select enabled auth methods per deployment (the config-file merge is by-index and cannot cleanly override an array)
94
+ * feat(web-framework): extend login-page auth gating from the OAuth buttons to every method — the `local` credentials form and the "or continue with" divider are now gated too, and a "no method configured" fallback is shown when nothing is enabled, so an SSO-only deployment presents no dead local form
95
+ * build(release): bump package version from `1.14.1` to `1.15.0`
96
+
97
+ ## Version 1.14.1
98
+
99
+ Security hardening for the web-server CodeQL findings raised after the scanner was modernized in CA-90 (CA-91).
100
+
101
+ * fix(web-framework): rewrite the default unprotected static-asset route matchers from the ambiguous `(?:.+/)*` to segment-anchored `(?:[^/]+/)*` — the previous form backtracked exponentially and is evaluated against the raw request path in `isUnprotectedRoute()` before authentication, making it a pre-authentication denial-of-service vector (CodeQL js/redos); the matched language for realistic asset paths is unchanged, and the matchers are now the `RE_STATIC_UNPROTECTED` / `RE_WELL_KNOWN_UNPROTECTED` module constants
102
+ * fix(web-framework): replace the login-fragment section stripper's `open[\s\S]*?close` global-regex removal with a linear `indexOf`-based `stripMarkerSpans()` helper (also used for the per-provider stripper), eliminating the polynomial-time rescan on hostile input (CodeQL js/polynomial-redos)
103
+ * docs(web-framework): document that Helmet's built-in Content-Security-Policy is intentionally disabled because a per-request, nonce-based CSP is enforced by `cspHeaderHandler()` on the following middleware — added an explanatory comment and an inline CodeQL suppression (the alert is a false positive)
104
+ * build(release): bump package version from `1.14.0` to `1.14.1`
105
+
106
+ ## Version 1.14.0
107
+
108
+ `TI_WEB_*` environment-variable overrides for the web server configuration, enabling 12-factor container deployments without per-environment config files (CA-90).
109
+
110
+ * feat(web-framework): add `applyWebConfigEnvOverrides( config, env = process.env )` (`#web-config-env`) — a pure helper applying `TI_WEB_HOST`, `TI_WEB_PORT`, `TI_WEB_USE_TLS`, `TI_WEB_TLS_CERT_PATH`, `TI_WEB_TLS_KEY_PATH`, and `TI_WEB_COOKIE_SECRET` overrides onto the merged `TiWebServer` configuration, only when each variable is defined (fully backward compatible)
111
+ * fix(web-framework): skip an enabled OpenID Connect provider that has no client ID instead of crashing the instance during discovery — an OAuth-less deployment (e.g. a container started without OAuth credentials) now boots on its remaining methods, with a warning, and reports the dropped provider as unavailable so a sign-in attempt against it fails per-request rather than at startup
112
+ * feat(web-framework): the login page now renders an OpenID provider button only when that provider is an effective enabled auth method — the web server passes the post-drop enabled methods to the app manager, which strips the Google/Azure button (and the whole "or continue with" section when no provider is available) from `frame-login.html` at render time
113
+ * build(release): bump package version from `1.13.2` to `1.14.0`
114
+
115
+ ## Version 1.13.0
116
+
117
+ A reusable role-based screen gate and a per-screen title override (back the competence screen-access work and the evaluation/scores screen split).
118
+
119
+ * feat(web-framework): the default `TiWebAppManager.verifyAccess` now enforces a fragment's declared `roles` — a fragment registered (via `addFragment`) with a `roles` array is served only to sessions holding at least one of them (otherwise rejected `E_SEC_UNAUTHORIZED_ACCESS` 403), while a fragment with no `roles` stays public. This makes role-restricted screens unreachable by direct URL, not merely hidden in the UI; apps just declare `roles` on `addFragment` — no `verifyAccess` override needed. Backward compatible: all existing role-less fragments remain public
120
+ * feat(web-framework): add `authorization.isAccessAllowed( requiredRoles, userRoles )` — a pure, unit-tested access decision (empty/absent roles = public; otherwise ≥1 overlap; no implicit hierarchy, so an `admin` gate is never satisfied by a numeric role) that backs the default `verifyAccess`
121
+ * feat(web-framework): add `tiApplication.setScreenTitle( title )` — a per-screen topbar/document-title override (cleared automatically on navigation) so a screen can correct its own title at runtime (e.g. a manager viewing another user's scores must not read "My …")
122
+ * build(release): bump package version from `1.12.0` to `1.13.0`
123
+
124
+ ## Version 1.12.0
125
+
126
+ Chart primitives gain legends + value labels for grouped bars and a legend for radar (backs the leaner competence evaluation results view) (CA-61).
127
+
128
+ * feat(web-framework): `ti-charts` grouped bars now render an optional swatch legend (`options.legend`) and per-bar value captions (`options.valueLabels`); radar charts render an optional legend, with a dashed swatch variant (`{ dashed: true }`) for dashed series such as an "expected" curve (CA-61)
129
+ * feat(web-framework): add `.ti-chart-bar-seg.tone-info`, `.ti-chart-legend-swatch.tone-info`, and `.ti-chart-legend-swatch.is-dashed` so grouped/radar source series and their legends share one colour scale across both themes (CA-61)
130
+ * feat(web-framework): `ti-charts` radar accepts an optional per-axis `tone`, applied as a `tone-*` class on the axis label so consumers can colour axis labels (e.g. by category) — threaded through `radarLayout` (CA-61)
131
+ * feat(web-framework): `ti-charts` grouped bars accept optional `options.barThickness` (bar height) and `options.valueFontSize` (value-caption font); value captions carry a dedicated `ti-chart-bar-value` class that intentionally sets no CSS `font-size`, so the renderer's `font-size` presentation attribute (default 4) actually governs — previously the caption also carried `ti-chart-bar-label`, whose CSS `font-size: 4px` overrode the attribute and made `valueFontSize` a no-op (CA-61)
132
+ * build(release): bump package version from `1.11.1` to `1.12.0`
133
+
134
+ ## Version 1.11.1
135
+
136
+ Post-review fix from the CA-72 CodeRabbit review (PR #85) on the login test-user panel.
137
+
138
+ * fix(web-framework): turning the "override roles (dev)" toggle OFF now always strips any persisted roles from the `ti-test-user` cookieeven when the selected employee is no longer in the panel's profile list so a stale roles array can't keep overriding the app's org-derived roles on the next login (CA-72)
139
+ * build(release): bump package version from `1.11.0` to `1.11.1`
140
+
141
+ ## Version 1.11.0
142
+
143
+ Login test-user panel defaults to identity-only injection so the app derives roles itself (CA-72).
144
+
145
+ * feat(web-framework): login test-user panel injects identity only by default (roles derived by the app); role injection becomes an opt-in dev override (CA-72)
146
+ * docs(web-framework): clarify the augmentSession contract (derive vs. override) (CA-72)
147
+ * build(release): bump package version from `1.10.4` to `1.11.0`
148
+
149
+ ## Version 1.10.4
150
+
151
+ Login test-user fixture follow-up (CA-71).
152
+
153
+ * chore(web-framework): login test-user `8` carries the `MANAGER` role (`[1, 2]`) so manager / self-manage scenarios (e.g. the competence Org Chart self-manage gate) can be exercised without re-seeding
154
+ * build(release): bump package version from `1.10.3` to `1.10.4`
155
+
156
+ ## Version 1.10.3
157
+
158
+ Developer-tooling and formatting touch-ups on the shared frontend bundle.
159
+
160
+ * chore(web-framework): the login test-user panel offers more seeded employees (3, 8, 9 alongside 22/20/1/4) so manager / direct-report / skip-level scenarios can be exercised without re-seeding
161
+ * style(web-framework): minor formatting of `formatException`'s return object
162
+ * build(release): bump package version from `1.10.2` to `1.10.3`
163
+
164
+ ## Version 1.10.2
165
+
166
+ Readability and scaling fixes for the chart primitives, surfaced while polishing the Statistics & Results screens (CA-61).
167
+
168
+ * fix(web-framework): stacked bar charts now caption each row (the group/cycle label plus an optional per-row value) and render an optional swatch legend driven by `spec.options.legend`, so a coverage "By group" chart reads as labelled bars instead of anonymous colour blocks; the row labels also land on the cross-cycle trend bars
169
+ * fix(css): horizontal bar charts opt out of the global `svg` `max-height` so bar thickness and label size stay identical regardless of row count — a tall org-wide chart is no longer uniformly scaled down and rendered finer than the same chart on a smaller subtree; the per-row geometry is trimmed for a cleaner look
170
+ * feat(css): `.ti-chart-legend` / `.ti-chart-legend-item` / `.ti-chart-legend-swatch` — a chart swatch legend whose colours route through the inherited grade/ink chart tokens
171
+ * build(release): bump package version from `1.10.1` to `1.10.2`
172
+
173
+ ## Version 1.10.1
174
+
175
+ Review fixes for the ti-chart primitives (Statistics & Results, CA-61, PR #83 — CodeRabbit pass).
176
+
177
+ * fix(web-framework): drillable heatmap/box marks get an accessible name; `renderChart` clears stale `data-ti-chart-empty`/`aria-label` on rerender; `renderStat` renders a missing value as an em dash; the provisional line-dot stroke follows its `tone-*` class
178
+ * fix(css): `.ti-chart-sr` uses `clip-path: inset(50%)` instead of the deprecated `clip` property
179
+ * build(release): bump package version from `1.10.0` to `1.10.1`
180
+
181
+ ## Version 1.10.0
182
+
183
+ ### Charting primitive library (Statistics & Results, CA-61)
184
+
185
+ * feat(web-framework): new `ti-charts.js` — a CSP-safe SVG charting library backing the competence Statistics & Results reporting. Eight primitives via a single `renderChart(figure, spec)` dispatcher: `gauge`, `bars` (stacked / grouped / diverging modes), `stat`, `scatter`, `heatmap` (sequential / diverging scales), `box`, `radar`, and `line` (mean + p25–p75 band, sparkline, stacked, dashed-provisional trailing segment). The pure layout helpers (`gaugeArcPath`, `barSegments`, `scatterLayout`, `heatmapLayout`, `boxLayout`, `radarLayout`, `lineLayout`, …) are unit-tested in isolation
186
+ * feat(web-framework): register the `x-ti-chart` Alpine CSP directive (binds a spec object to a host `<figure>`); every chart builds its SVG with `createElementNS` + `setAttribute` only (never `element.style.*` except `setProperty("--var")`) and ships a visually-hidden `.ti-chart-sr` accessibility table
187
+ * feat(css): `.ti-chart-*` styles + per-type `figure[data-ti-chart-type]` size caps + `--chart-seq-1…5` sequential ramp tokens and grade/tone colours in both themes (daylight + black-glass)
188
+ * build(release): bump package version from `1.9.3` to `1.10.0`
189
+
190
+ ## Version 1.9.3
191
+
192
+ * feat(css): an empty `.ti-grade-chip` (a competency whose rating is still awaited) now renders an hourglass glyph via `::before` instead of a literal dash, so "awaiting rating" reads as a clear visual state wherever an empty grade chip is shown to a permitted viewer
193
+ * build(release): bump package version from `1.9.2` to `1.9.3`
194
+
195
+ ## Version 1.9.2
196
+
197
+ * feat(css): add `.ti-spacer` a flexible spacer (`flex: 1 1 auto`) that pushes following siblings to the far end of a flex row/column. Promotes the bespoke per-screen `competence-empmgmt-actions-spacer` into a shared primitive, now consumed by the employee-management actions panel and the evaluation screen's team-feedback finalize bar
198
+ * build(release): bump package version from `1.9.1` to `1.9.2`
199
+
200
+ ## Version 1.9.1
201
+
202
+ * build(deps): upgrade `ajv` from ^6.15.0 to ^8.20.0 ajv 8 renamed the validation-error `dataPath` (dot style) to `instancePath` (JSON Pointer)
203
+ * fix(config-registry): normalize ajv 8's `instancePath` back to the dot/bracket data path the registry has always exposed on schema issues (e.g. `.competencies.E1-1.name`, array indices as `[0]`), so the public `ConfigValidationIssue.path` contract is unchanged across the upgrade; the ajv compile options (`meta`, `schemaId: "$id"`, `validateSchema: false`) and Draft-07 handling are unchanged
204
+ * build(deps): update `helmet` from ^8.1.0 to ^8.2.0
205
+
206
+ ## Version 1.9.0
207
+
208
+ * feat(css): add `.ti-panel-body-intro` the canonical description/intro line under a `.ti-panel-head` (`--fs-sm`, secondary foreground, `0 var(--s-3) var(--s-5)` padding, 1.5 line-height); replaces the per-screen intro paragraphs that screens used to hand-style
209
+ * refactor(css): tighten the key/value primitives `.ti-kv-label` is now an uppercase `--fs-xs` 600-weight caption (0.05em letter-spacing); `.ti-kv-value` is `--fs-sm` 400-weight for a consistent, scannable key/value rhythm across screens
210
+ * refactor(css): drop the redundant `margin-left: auto` from `.ti-panel-head-aside` (the panel head already positions it via its flex layout)
211
+ * fix(sidebar): the user-profile flyout actions (Profile, Settings, Logout) now actually fire. The menu items bind their `hx-*` attributes through Alpine (`x-bind`), which HTMX does not pick up on its initial document scan — so the buttons previously only closed the flyout. The flyout now runs `htmx.process` on its panel when it opens (idempotent on re-open), wiring up each button's `hx-get`/`hx-post`/`hx-target`/`hx-swap`
212
+ * fix(sidebar): the role/department line under the user name no longer overflows the fixed-width sidebar — `.ti-sidebar-user-name` and `.ti-sidebar-user-sub` truncate with an ellipsis, and `.ti-sidebar-user-text` gets `flex: 1` so it bounds the text column
213
+ * style(css): expand the `.ti-icon` size-modifier one-liners (`.xs`/`.sm`/`.md`/`.lg`/`.xl`) to block form for consistency with the rest of the sheet
214
+ * build(release): bump package version from `1.8.0` to `1.9.0`
215
+
216
+ ## Version 1.8.0
217
+
218
+ * feat(notifications): notifications can now show a secondary **details** line under the generic message. `tiApplication.formatException` returns `{ message, details }` (resolved from the exception's `data.details`, falling back to the raw text for non-localized messages) and `tiApplication.notify` accepts that payload — so an error like "The request parameters are not recognized or not supported." now also shows the specifics (e.g. "Competency codes not in the 'QE' pool: …") in a smaller, muted font. The returned object stringifies to its message, so existing string usages keep working unchanged
219
+ * fix(css): raise the toast stack above the modal layer (`z-index` 1100 1300; the modal backdrop is 1200) so a notification raised while a modal is open is no longer hidden behind it
220
+
221
+ ## Version 1.7.1
222
+
223
+ * fix(web-handlers): web-application request errors that carry no explicit `httpCode` are no longer reported as `500`. A new `resolveHttpCode` derives the status from the exception code — request-validation and application-logic errors (`E_WEB_*` / `E_APP_*`) map to `422 Unprocessable Content`, security (`E_SEC_*`) to `403`, resource not-found/already-exists to `404`/`409`, and method/URI/content errors to `405`/`404`/`415`; only genuine internal, communication, and unknown errors still default to `500`. An explicit `httpCode` on the exception always wins. Applied in both the `/app` request handler (`formatException`) and the default error handler
224
+
225
+ ## Version 1.7.0
226
+
227
+ * feat(css): introduce `.ti-data-grid` family — `.ti-data-grid`, `.ti-data-grid-head`, `.ti-data-grid-rows`, `.ti-data-grid-row` with shared `--ti-grid-cols` template; row state modifiers `.is-current` (accent-soft, "current user") and `.is-selected` (accent-soft + left accent bar); wrapper variants `.bordered` (horizontal dividers for tabular displays) and `.compact` (denser padding); cell utilities `.ti-cell-center` / `.ti-cell-right` for per-cell alignment
228
+ * feat(css): introduce `.ti-page-head` as a vertical block stack (eyebrow above title, subtitle below) using `--fs-xs` for the eyebrow and clamping subtitle width at 60ch
229
+ * feat(css): introduce a reusable `.ti-form*` family `.ti-form`, `.ti-form-section`, `.ti-form-section-title`, `.ti-form-grid` (with `.cols-1` / `.cols-3` modifiers), `.ti-form-row` (with `.wide` for grid-spanning), `.ti-form-readonly`, `.ti-form-hint`, `.ti-form-error`, `.ti-form-actions`, `.ti-form-state` (with `.saved` / `.unsaved`); single responsive collapse to one column under 720px
230
+ * feat(css): extend `.ti-panel-head` with sub-elements — `.ti-panel-head-icon` (32x32 framed icon slot), `.ti-panel-head-text` (title + subtitle stack inside a flex row), `.ti-panel-title-aside` (inline qualifier next to the title), `.ti-panel-subtitle` (dimmed sub line), `.ti-panel-head-aside` (right-aligned read-only info with left-border separator), and the `.bar` modifier (sunken full-width banner)
231
+ * feat(icons)!: rebase `.ti-icon` on `background-color: currentColor` so icons inherit the surrounding text colour; add size modifiers `.xs` (12px), `.sm` (14px), `.md` (16px), `.lg` (24px), `.xl` (32px); add `.legacy-gray` modifier to preserve the previous gray/hover behaviour for consumers that rely on the fixed colour scheme
232
+ * feat(icons): add 21 new `.ti-icon` variants (lucide / feather style, 24x24 viewBox) — `plus`, `close`, `check`, `check-clipboard`, `send`, `search`, `clock`, `warning-triangle`, `info-circle`, `bell`, `check-circle`, `eye`, `calendar-blank`, `user`, `users`, `briefcase`, `folder`, `book`, `help-circle`, `bar-chart`, `chevron-left`, `chevron-right`, `dashboard-grid`, `cycles-loop`, `sun`
233
+ * feat(icons): add `.ti-icon.moon` mask variant so theme toggles can mirror the target mode
234
+ * feat(framework): add `tiApplication.hasRole(roleCode)` helper that does the array-shape check in plain JS (the Alpine CSP build does not expose `Array` to its expression evaluator, so `Array.isArray(...)` written inline in a template raises `Undefined variable: Array`)
235
+ * feat(framework): add `tiApplication.topbarPrimaryCta` store slot plus `setTopbarPrimaryCta` / `setTopbarPrimaryCtaDisabled` API for per-screen CTA buttons in the topbar; auto-cleared on screen navigation so each screen owns its slot
236
+ * feat(css): native select chevron replaced by a custom down-chevron SVG positioned at right: 10px / 14x14; padding-right reserves the slot; glass theme overrides the SVG stroke colour because `background-image` can't pick up `currentColor`
237
+ * feat(css): subdue `::-webkit-calendar-picker-indicator` to opacity 0.7 (1 on hover) so date-input visual weight matches the chevron
238
+ * feat(notification bar): replace inline toast SVGs with `.ti-icon` mask classes (success check, danger close, warn triangle, info circle, close button)
239
+ * feat(sidebar): replace inline navigation SVGs with `.ti-icon` mask classes (collapse chevron, dashboard home, sun theme toggle)
240
+ * refactor(css): drop the screen-specific page-header, form, and tabular-layout CSS that duplicated framework primitives; all in-tree screens (`frame-employees-list`, `frame-cycles`, `frame-cycle-setup`, `frame-competence-evaluation`, `frame-new-evaluation`, `frame-manager-calendar`, `frame-interview-schedule`, `frame-employee-management`) now consume `.ti-page-head`, `.ti-data-grid`, `.ti-form*`, and `.ti-panel-head*` instead
241
+ * docs(modal): doc-block on `.ti-modal-*` confirming it as the canonical shared primitive (introduced in 1.6.3 via the competence cycle-setup work)
242
+ * build(release): bump package version from `1.6.3` to `1.7.0`
243
+
244
+ ## Version 1.6.3
245
+
246
+ * feat(css): add `--ti-internal-padding` CSS variable to the design token system
247
+ * feat(css): add `--ti-border-color` CSS variable for consistent border theming
248
+ * feat(icons): add `.ti-icon.calendar` and `.ti-icon.schedule` icon variants with hover states
249
+ * feat(css): add `.ti-data-value.fill-space` modifier for flex-grow behavior in inline data value layouts
250
+ * fix(css): remove `min-width: 120px` constraint from `.ti-button.inline` for more flexible sizing
251
+ * fix(css): update z-index stacking values for dropdown and overlay elements to prevent layering conflicts
252
+ * build(deps): update `openid-client` from ^6.8.2 to ^6.8.4
253
+ * build(deps): update bundled `@alpinejs/csp` from ^3.15.11 to ^3.15.12
254
+ * build(deps): update bundled `htmx.org` from ^2.0.8 to ^2.0.10
255
+ * build(static): refresh bundled Alpine.js CSP and HTMX library files to match updated dependency versions
256
+
257
+ ## Version 1.6.2
258
+
259
+ * feat(ui): replace Material Symbols usage with framework-native `.ti-icon` classes across sidebar flyouts, login actions, and notification bar
260
+ * feat(sidebar): merge administration and user flyout menus into a single `sidebarApplicationMenu` with configurable menu icon and updated actions
261
+ * feat(css): add embedded SVG mask icon variants (`app-menu`, `dashboard`, `settings`, `error`, `user-profile`, `login`, `logout`, `internet`) and increase default icon size to `24px`
262
+ * refactor(theme): remove Material Symbols-specific icon styling from the black-glass theme
263
+ * build(static): remove external Google Material Symbols stylesheet import from static `index.html`
264
+ * build(release): bump package version from `1.6.1` to `1.6.2`
265
+
266
+ ## Version 1.6.1
267
+
268
+ * feat(css): add inline button support via `.ti-button.inline` and new `--ti-button-inline-height` design token
269
+ * refactor(ui): update sidebar flyout positioning logic to use shared `tiToolbox` viewport helpers (`getVisibleBox`, `clampToBox`)
270
+ * fix(ui): fix the call to utility functions `getVisibleBox` and `clampToBox` in the sidebar flyout component
271
+ * build(release): bump package version from `1.6.0` to `1.6.1`
272
+
273
+ ## Version 1.6.0
274
+
275
+ * feat(toolbox): add Alpine.js `tiToolbox` store with shared utility methods (`deepMerge`, `deepFreeze`, `structuredClone`, `formatDate`, viewport helpers, and cookie access)
276
+ * feat(ui): move sidebar menu configuration into `ti-framework.js` and register `tiComponentsConfig` during Alpine.js initialization
277
+ * refactor(static): remove legacy `ti-user-interface.js` from static assets and stop loading it from `index.html`
278
+ * refactor(components): update framework components to consume toolbox utilities through Alpine stores
279
+ * refactor(docs): add and expand JSDoc typedefs and method-level documentation in `ti-framework.js`
280
+ * build(release): bump package version from `1.5.3` to `1.6.0`
281
+
282
+ ## Version 1.5.3
283
+
284
+ * feat(framework): add `openScreen` method for in-app navigation
285
+ * fix(auth): add explicit HTTP `401` status to authentication failure
286
+
287
+ ## Version 1.5.2
288
+
289
+ * feat(framework): expand application API by improving `sendRequest`, `notify`, and `getLabel` methods
290
+ * feat(tooltip): add helper methods `getTooltipMessage`, `handleEnter`, `handleLeave`, `showTooltip`, `hideTooltip` to the tooltip component
291
+ * feat(css): improve styles and style structure
292
+
293
+ ## Version 1.5.1
294
+
295
+ * feat(framework): add `isValidDate` utility function for validating Date instances
296
+ * feat(framework): add `deepFreeze` utility function for recursive object freezing
297
+ * feat(framework): add `getLabel` method on application configuration for nested label resolution with dot notation
298
+ * feat(framework): add Alpine.js directive `x-text-label` for runtime label translation
299
+ * feat(framework): add a configuration object to replace labels object with enhanced structure
300
+ * feat(config): add authentication state (`auth.isAuthenticated`) to config endpoint response
301
+ * feat(placeholder): add inner content capture and injection for placeholder replacement
302
+ * feat(tooltip): add a new tooltip component with Alpine.js integration and positioning
303
+ * feat(css): add CSS custom properties for padding, margin, and font-family
304
+ * feat(css): add tooltip styling variables (background, foreground, size, shadow, arrow)
305
+ * feat(css): add `.ti-content.pane` block with flex layout and overflow handling
306
+ * feat(css): add error color, flyout item shadows, and separator color variables
307
+ * feat(css): add `.ti-glass-btn-black.large` variant with left-justified content
308
+ * refactor(framework): change user initialization from `undefined` to `null`
309
+ * refactor(framework): improve request failure handling with proper error rejection
310
+ * refactor(css): replace hard-coded spacing with CSS variables across components
311
+ * refactor(css): add `overflow: hidden` to body and `.ti-main` for better layout control
312
+ * refactor(css): convert color and styling values to CSS variables throughout
313
+ * refactor(handlers): add `convertUriToString` helper for safe URI object stringification
314
+ * refactor(handlers): add a request context object (query, params, headers, url, method) to JSON responses
315
+ * refactor(handlers): augment user data with default employeeID and roles in the user information handler
316
+ * build(deps): update express from ^5.1.0 to ^5.2.1
317
+ * build(deps): update express-session from ^1.18.2 to ^1.19.0
318
+ * build(deps): update lodash from ^4.17.21 to ^4.17.23
319
+ * build(deps): update openid-client from ^6.8.1 to ^6.8.2
320
+ * build(deps): update `@alpinejs/csp` from ^3.15.2 to ^3.15.8
321
+ * build(engines): update Node.js requirement from >=18.0.0 to >=20.0.0
322
+
323
+ ## Version 1.5.0
324
+
325
+ * feat(web-app)!: change `TiWebAppManager` to be an abstract class
326
+ * feat(web-app): rename class `WebAppManager` to `TiWebAppManager` and add a static file caching mechanism
327
+ * feat(web-app): add `addFragment` method with override protection for custom HTML fragment registration
328
+ * feat(web-app): add `webAppIdentifier` getter to expose application identifier
329
+ * feat(web-server): add support for dynamic web application instantiation from config via `classPath`
330
+ * feat(web-server): add `defineWebApplicationRoutes` and `defineUnprotectedRoutes` extension points
331
+ * feat(web-server): add `endpointEnabled` flag to conditionally enable API endpoint proxy
332
+ * feat(web-server): add serving for `.well-known` directory for web standards compliance
333
+ * feat(package): add public exports for `./web-application` and `./web-server` subpaths
334
+ * feat(package): add `files` whitelist and repository metadata (homepage, bugs URL, git repository)
335
+ * feat(package): add Node.js version requirement (>=18.0.0) via the `engines` field
336
+ * feat(build): add a post-install script to vendor HTMX and Alpine.js CSP libraries locally
337
+ * refactor(web-app): replace `fullPublicPath` with `staticContentPaths` array for multi-path static content resolution
338
+ * refactor(web-app): add file location search algorithm with caching via `#locateStaticFile` method
339
+ * refactor(web-app): update `transformHtml` signature to remove `fullPublicPath` parameter
340
+ * refactor(web-app): update `assembleHtmlView` to accept `staticContentPaths` instead of `fullPublicPath`
341
+ * refactor(web-server): replace the single static path with configurable `staticContentPaths` array
342
+ * refactor(web-server): merge web server default config with the provided service config in constructor
343
+ * refactor(web-server): load web server default config directly from the package JSON import instead of the ENV configuration
344
+ * refactor(web-server): improve TLS initialization error handling to reject promise instead of throw
345
+ * refactor(package): reorganize imports from `./server/...` to `./bin/...` and `./components/...` paths
346
+ * refactor(package): move `@alpinejs/csp` from dependencies to devDependencies
347
+ * build(static): replace CDN script references with local copies for HTMX and Alpine.js CSP
348
+ * build(env): remove `TI_INSTANCE_CONFIG` and update `TI_LOCALIZATION_LABELS_PATH` to use `bin/localization/` path
349
+ * build(env): add `TI_AUDITING_LOG_MIN_LEVEL` configuration variable
350
+ * fix(ui): change `aria-expanded` binding from string to boolean in the sidebar flyout component
351
+ * fix(ui): remove incorrect `type="button"` attribute from `Home` anchor element
352
+ * docs: improve various documentation comments and class descriptions
353
+
354
+ ## Version 1.4.0
355
+
356
+ * feat(ui): add a notification bar component with Alpine.js integration and auto-dismiss functionality
357
+ * feat(ui): add CSS variables and styles for the notification system
358
+ * feat(routes): add `/not-found` fragment and route for 404 error pages
359
+ * feat(routes): add `/app/error` route for error handling testing - will be removed later
360
+ * feat(routes): add `/app/config` data endpoint for serving application configuration
361
+ * feat(localization): add language property to `User` class with getter and JSON serialization
362
+ * feat(localization): integrate localization module for label management and localized error messages
363
+ * feat(session): add language property to session data populated from user or service configuration
364
+ * feat(handlers): add response type detection helper `isAcceptingResponseType` for content negotiation
365
+ * feat(handlers): enhance error responses with localized messages via localization module
366
+ * feat(handlers): add HTMX-aware error handling with HX-Redirect and HX-Retarget headers
367
+ * feat(handlers): implement `processDataRequest` method in WebAppManager for serving data resources
368
+ * feat(config): add a language configuration option to the `WebServiceConfiguration` object
369
+ * refactor(handlers): delegate error handling from resource protection to error middleware
370
+ * refactor(handlers): improve 401/404 response handling by routing through exceptions and middleware
371
+ * refactor(handlers): enhance CSRF and origin validation to use error middleware instead of direct responses
372
+ * refactor(handlers): improve service call error handling to raise exceptions and delegate to middleware
373
+ * refactor(handlers): refactor invalid route handler to raise exceptions instead of direct 404 responses
374
+ * fix(types): correct ExpressRequest typedef from `import("express").req` to `import("express").Request`
375
+ * build(config): update publicPath from `packages/web-framework/bin/static` to `bin/static`
376
+ * build(config): update TLS certificate paths to use relative `bin/tls/` paths
377
+ * build(env): update `TI_INSTANCE_CLASS` and `TI_INSTANCE_CONFIG` paths to use relative `bin/` paths
378
+ * build(env): add `TI_LOCALIZATION_LABELS_PATH` environment variable for custom labels
379
+ * build(run): update IDE run configuration to use relative paths and the correct working directory
380
+ * build(labels): add an empty `web-server-labels.json` file for custom localization labels
381
+
382
+ ## Version 1.3.0
383
+
384
+ * feat: first working prototype version