@ti-engine/web-framework 1.13.1

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 (42) hide show
  1. package/.env +4 -0
  2. package/CHANGELOG.md +274 -0
  3. package/README.md +26 -0
  4. package/bin/build/post-install.js +18 -0
  5. package/bin/localization/web-server-labels.json +28 -0
  6. package/bin/static/.well-known/appspecific/com.chrome.devtools.json +6 -0
  7. package/bin/static/favicon.ico +0 -0
  8. package/bin/static/fragments/components/component-notification-bar.html +21 -0
  9. package/bin/static/fragments/components/component-sidebar-flyout.html +37 -0
  10. package/bin/static/fragments/components/component-sidebar.html +33 -0
  11. package/bin/static/fragments/components/component-tooltip.html +11 -0
  12. package/bin/static/fragments/components/component-topbar.html +6 -0
  13. package/bin/static/fragments/frame-administration.html +3 -0
  14. package/bin/static/fragments/frame-application.html +19 -0
  15. package/bin/static/fragments/frame-dashboard.html +3 -0
  16. package/bin/static/fragments/frame-login.html +105 -0
  17. package/bin/static/fragments/frame-not-found.html +3 -0
  18. package/bin/static/fragments/frame-profile.html +3 -0
  19. package/bin/static/index.html +23 -0
  20. package/bin/static/scripts/lib/alpinejs-csp.min.js +7 -0
  21. package/bin/static/scripts/lib/htmx.min.js +1 -0
  22. package/bin/static/scripts/lib/safe-nonce.min.js +1 -0
  23. package/bin/static/scripts/ti-charts.js +1591 -0
  24. package/bin/static/scripts/ti-framework.css +3195 -0
  25. package/bin/static/scripts/ti-framework.js +1427 -0
  26. package/bin/static/scripts/ti-theme-black-glass.css +216 -0
  27. package/bin/static/scripts/ti-theme-daylight.css +87 -0
  28. package/bin/web-app-manager.js +564 -0
  29. package/bin/web-server.js +604 -0
  30. package/bin/web-server.json +49 -0
  31. package/components/admin-config-handlers.js +92 -0
  32. package/components/auth-manager.js +344 -0
  33. package/components/authorization.js +135 -0
  34. package/components/config-change-notifier.js +98 -0
  35. package/components/config-registry.js +246 -0
  36. package/components/config-service.js +349 -0
  37. package/components/config-store.js +246 -0
  38. package/components/definitions.types.js +26 -0
  39. package/components/session-store.js +111 -0
  40. package/components/user.js +133 -0
  41. package/components/web-handlers.js +765 -0
  42. package/package.json +66 -0
package/.env ADDED
@@ -0,0 +1,4 @@
1
+ TI_AUDITING_LOG_MIN_LEVEL=200
2
+ TI_INSTANCE_CLASS=bin/web-server.js
3
+ TI_INSTANCE_NAME=ti-web-server
4
+ TI_LOCALIZATION_LABELS_PATH=bin/localization/web-server-labels.json
package/CHANGELOG.md ADDED
@@ -0,0 +1,274 @@
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.13.0
6
+
7
+ A reusable role-based screen gate and a per-screen title override (back the competence screen-access work and the evaluation/scores screen split).
8
+
9
+ * 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
10
+ * 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`
11
+ * 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 …")
12
+ * build(release): bump package version from `1.12.0` to `1.13.0`
13
+
14
+ ## Version 1.12.0
15
+
16
+ Chart primitives gain legends + value labels for grouped bars and a legend for radar (backs the leaner competence evaluation results view) (CA-61).
17
+
18
+ * 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)
19
+ * 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)
20
+ * 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)
21
+ * 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)
22
+ * build(release): bump package version from `1.11.1` to `1.12.0`
23
+
24
+ ## Version 1.11.1
25
+
26
+ Post-review fix from the CA-72 CodeRabbit review (PR #85) on the login test-user panel.
27
+
28
+ * 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)
29
+ * build(release): bump package version from `1.11.0` to `1.11.1`
30
+
31
+ ## Version 1.11.0
32
+
33
+ Login test-user panel defaults to identity-only injection so the app derives roles itself (CA-72).
34
+
35
+ * 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)
36
+ * docs(web-framework): clarify the augmentSession contract (derive vs. override) (CA-72)
37
+ * build(release): bump package version from `1.10.4` to `1.11.0`
38
+
39
+ ## Version 1.10.4
40
+
41
+ Login test-user fixture follow-up (CA-71).
42
+
43
+ * 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
44
+ * build(release): bump package version from `1.10.3` to `1.10.4`
45
+
46
+ ## Version 1.10.3
47
+
48
+ Developer-tooling and formatting touch-ups on the shared frontend bundle.
49
+
50
+ * 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
51
+ * style(web-framework): minor formatting of `formatException`'s return object
52
+ * build(release): bump package version from `1.10.2` to `1.10.3`
53
+
54
+ ## Version 1.10.2
55
+
56
+ Readability and scaling fixes for the chart primitives, surfaced while polishing the Statistics & Results screens (CA-61).
57
+
58
+ * 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
59
+ * 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
60
+ * 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
61
+ * build(release): bump package version from `1.10.1` to `1.10.2`
62
+
63
+ ## Version 1.10.1
64
+
65
+ Review fixes for the ti-chart primitives (Statistics & Results, CA-61, PR #83 — CodeRabbit pass).
66
+
67
+ * 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
68
+ * fix(css): `.ti-chart-sr` uses `clip-path: inset(50%)` instead of the deprecated `clip` property
69
+ * build(release): bump package version from `1.10.0` to `1.10.1`
70
+
71
+ ## Version 1.10.0
72
+
73
+ ### Charting primitive library (Statistics & Results, CA-61)
74
+
75
+ * 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
76
+ * 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
77
+ * 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)
78
+ * build(release): bump package version from `1.9.3` to `1.10.0`
79
+
80
+ ## Version 1.9.3
81
+
82
+ * 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
83
+ * build(release): bump package version from `1.9.2` to `1.9.3`
84
+
85
+ ## Version 1.9.2
86
+
87
+ * 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
88
+ * build(release): bump package version from `1.9.1` to `1.9.2`
89
+
90
+ ## Version 1.9.1
91
+
92
+ * 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)
93
+ * 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
94
+ * build(deps): update `helmet` from ^8.1.0 to ^8.2.0
95
+
96
+ ## Version 1.9.0
97
+
98
+ * 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
99
+ * 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
100
+ * refactor(css): drop the redundant `margin-left: auto` from `.ti-panel-head-aside` (the panel head already positions it via its flex layout)
101
+ * 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`
102
+ * 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
103
+ * 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
104
+ * build(release): bump package version from `1.8.0` to `1.9.0`
105
+
106
+ ## Version 1.8.0
107
+
108
+ * 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
109
+ * 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
110
+
111
+ ## Version 1.7.1
112
+
113
+ * 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
114
+
115
+ ## Version 1.7.0
116
+
117
+ * 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
118
+ * 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
119
+ * 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
120
+ * 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)
121
+ * 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
122
+ * 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`
123
+ * feat(icons): add `.ti-icon.moon` mask variant so theme toggles can mirror the target mode
124
+ * 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`)
125
+ * 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
126
+ * 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`
127
+ * feat(css): subdue `::-webkit-calendar-picker-indicator` to opacity 0.7 (1 on hover) so date-input visual weight matches the chevron
128
+ * feat(notification bar): replace inline toast SVGs with `.ti-icon` mask classes (success check, danger close, warn triangle, info circle, close button)
129
+ * feat(sidebar): replace inline navigation SVGs with `.ti-icon` mask classes (collapse chevron, dashboard home, sun theme toggle)
130
+ * 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
131
+ * 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)
132
+ * build(release): bump package version from `1.6.3` to `1.7.0`
133
+
134
+ ## Version 1.6.3
135
+
136
+ * feat(css): add `--ti-internal-padding` CSS variable to the design token system
137
+ * feat(css): add `--ti-border-color` CSS variable for consistent border theming
138
+ * feat(icons): add `.ti-icon.calendar` and `.ti-icon.schedule` icon variants with hover states
139
+ * feat(css): add `.ti-data-value.fill-space` modifier for flex-grow behavior in inline data value layouts
140
+ * fix(css): remove `min-width: 120px` constraint from `.ti-button.inline` for more flexible sizing
141
+ * fix(css): update z-index stacking values for dropdown and overlay elements to prevent layering conflicts
142
+ * build(deps): update `openid-client` from ^6.8.2 to ^6.8.4
143
+ * build(deps): update bundled `@alpinejs/csp` from ^3.15.11 to ^3.15.12
144
+ * build(deps): update bundled `htmx.org` from ^2.0.8 to ^2.0.10
145
+ * build(static): refresh bundled Alpine.js CSP and HTMX library files to match updated dependency versions
146
+
147
+ ## Version 1.6.2
148
+
149
+ * feat(ui): replace Material Symbols usage with framework-native `.ti-icon` classes across sidebar flyouts, login actions, and notification bar
150
+ * feat(sidebar): merge administration and user flyout menus into a single `sidebarApplicationMenu` with configurable menu icon and updated actions
151
+ * 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`
152
+ * refactor(theme): remove Material Symbols-specific icon styling from the black-glass theme
153
+ * build(static): remove external Google Material Symbols stylesheet import from static `index.html`
154
+ * build(release): bump package version from `1.6.1` to `1.6.2`
155
+
156
+ ## Version 1.6.1
157
+
158
+ * feat(css): add inline button support via `.ti-button.inline` and new `--ti-button-inline-height` design token
159
+ * refactor(ui): update sidebar flyout positioning logic to use shared `tiToolbox` viewport helpers (`getVisibleBox`, `clampToBox`)
160
+ * fix(ui): fix the call to utility functions `getVisibleBox` and `clampToBox` in the sidebar flyout component
161
+ * build(release): bump package version from `1.6.0` to `1.6.1`
162
+
163
+ ## Version 1.6.0
164
+
165
+ * feat(toolbox): add Alpine.js `tiToolbox` store with shared utility methods (`deepMerge`, `deepFreeze`, `structuredClone`, `formatDate`, viewport helpers, and cookie access)
166
+ * feat(ui): move sidebar menu configuration into `ti-framework.js` and register `tiComponentsConfig` during Alpine.js initialization
167
+ * refactor(static): remove legacy `ti-user-interface.js` from static assets and stop loading it from `index.html`
168
+ * refactor(components): update framework components to consume toolbox utilities through Alpine stores
169
+ * refactor(docs): add and expand JSDoc typedefs and method-level documentation in `ti-framework.js`
170
+ * build(release): bump package version from `1.5.3` to `1.6.0`
171
+
172
+ ## Version 1.5.3
173
+
174
+ * feat(framework): add `openScreen` method for in-app navigation
175
+ * fix(auth): add explicit HTTP `401` status to authentication failure
176
+
177
+ ## Version 1.5.2
178
+
179
+ * feat(framework): expand application API by improving `sendRequest`, `notify`, and `getLabel` methods
180
+ * feat(tooltip): add helper methods `getTooltipMessage`, `handleEnter`, `handleLeave`, `showTooltip`, `hideTooltip` to the tooltip component
181
+ * feat(css): improve styles and style structure
182
+
183
+ ## Version 1.5.1
184
+
185
+ * feat(framework): add `isValidDate` utility function for validating Date instances
186
+ * feat(framework): add `deepFreeze` utility function for recursive object freezing
187
+ * feat(framework): add `getLabel` method on application configuration for nested label resolution with dot notation
188
+ * feat(framework): add Alpine.js directive `x-text-label` for runtime label translation
189
+ * feat(framework): add a configuration object to replace labels object with enhanced structure
190
+ * feat(config): add authentication state (`auth.isAuthenticated`) to config endpoint response
191
+ * feat(placeholder): add inner content capture and injection for placeholder replacement
192
+ * feat(tooltip): add a new tooltip component with Alpine.js integration and positioning
193
+ * feat(css): add CSS custom properties for padding, margin, and font-family
194
+ * feat(css): add tooltip styling variables (background, foreground, size, shadow, arrow)
195
+ * feat(css): add `.ti-content.pane` block with flex layout and overflow handling
196
+ * feat(css): add error color, flyout item shadows, and separator color variables
197
+ * feat(css): add `.ti-glass-btn-black.large` variant with left-justified content
198
+ * refactor(framework): change user initialization from `undefined` to `null`
199
+ * refactor(framework): improve request failure handling with proper error rejection
200
+ * refactor(css): replace hard-coded spacing with CSS variables across components
201
+ * refactor(css): add `overflow: hidden` to body and `.ti-main` for better layout control
202
+ * refactor(css): convert color and styling values to CSS variables throughout
203
+ * refactor(handlers): add `convertUriToString` helper for safe URI object stringification
204
+ * refactor(handlers): add a request context object (query, params, headers, url, method) to JSON responses
205
+ * refactor(handlers): augment user data with default employeeID and roles in the user information handler
206
+ * build(deps): update express from ^5.1.0 to ^5.2.1
207
+ * build(deps): update express-session from ^1.18.2 to ^1.19.0
208
+ * build(deps): update lodash from ^4.17.21 to ^4.17.23
209
+ * build(deps): update openid-client from ^6.8.1 to ^6.8.2
210
+ * build(deps): update `@alpinejs/csp` from ^3.15.2 to ^3.15.8
211
+ * build(engines): update Node.js requirement from >=18.0.0 to >=20.0.0
212
+
213
+ ## Version 1.5.0
214
+
215
+ * feat(web-app)!: change `TiWebAppManager` to be an abstract class
216
+ * feat(web-app): rename class `WebAppManager` to `TiWebAppManager` and add a static file caching mechanism
217
+ * feat(web-app): add `addFragment` method with override protection for custom HTML fragment registration
218
+ * feat(web-app): add `webAppIdentifier` getter to expose application identifier
219
+ * feat(web-server): add support for dynamic web application instantiation from config via `classPath`
220
+ * feat(web-server): add `defineWebApplicationRoutes` and `defineUnprotectedRoutes` extension points
221
+ * feat(web-server): add `endpointEnabled` flag to conditionally enable API endpoint proxy
222
+ * feat(web-server): add serving for `.well-known` directory for web standards compliance
223
+ * feat(package): add public exports for `./web-application` and `./web-server` subpaths
224
+ * feat(package): add `files` whitelist and repository metadata (homepage, bugs URL, git repository)
225
+ * feat(package): add Node.js version requirement (>=18.0.0) via the `engines` field
226
+ * feat(build): add a post-install script to vendor HTMX and Alpine.js CSP libraries locally
227
+ * refactor(web-app): replace `fullPublicPath` with `staticContentPaths` array for multi-path static content resolution
228
+ * refactor(web-app): add file location search algorithm with caching via `#locateStaticFile` method
229
+ * refactor(web-app): update `transformHtml` signature to remove `fullPublicPath` parameter
230
+ * refactor(web-app): update `assembleHtmlView` to accept `staticContentPaths` instead of `fullPublicPath`
231
+ * refactor(web-server): replace the single static path with configurable `staticContentPaths` array
232
+ * refactor(web-server): merge web server default config with the provided service config in constructor
233
+ * refactor(web-server): load web server default config directly from the package JSON import instead of the ENV configuration
234
+ * refactor(web-server): improve TLS initialization error handling to reject promise instead of throw
235
+ * refactor(package): reorganize imports from `./server/...` to `./bin/...` and `./components/...` paths
236
+ * refactor(package): move `@alpinejs/csp` from dependencies to devDependencies
237
+ * build(static): replace CDN script references with local copies for HTMX and Alpine.js CSP
238
+ * build(env): remove `TI_INSTANCE_CONFIG` and update `TI_LOCALIZATION_LABELS_PATH` to use `bin/localization/` path
239
+ * build(env): add `TI_AUDITING_LOG_MIN_LEVEL` configuration variable
240
+ * fix(ui): change `aria-expanded` binding from string to boolean in the sidebar flyout component
241
+ * fix(ui): remove incorrect `type="button"` attribute from `Home` anchor element
242
+ * docs: improve various documentation comments and class descriptions
243
+
244
+ ## Version 1.4.0
245
+
246
+ * feat(ui): add a notification bar component with Alpine.js integration and auto-dismiss functionality
247
+ * feat(ui): add CSS variables and styles for the notification system
248
+ * feat(routes): add `/not-found` fragment and route for 404 error pages
249
+ * feat(routes): add `/app/error` route for error handling testing - will be removed later
250
+ * feat(routes): add `/app/config` data endpoint for serving application configuration
251
+ * feat(localization): add language property to `User` class with getter and JSON serialization
252
+ * feat(localization): integrate localization module for label management and localized error messages
253
+ * feat(session): add language property to session data populated from user or service configuration
254
+ * feat(handlers): add response type detection helper `isAcceptingResponseType` for content negotiation
255
+ * feat(handlers): enhance error responses with localized messages via localization module
256
+ * feat(handlers): add HTMX-aware error handling with HX-Redirect and HX-Retarget headers
257
+ * feat(handlers): implement `processDataRequest` method in WebAppManager for serving data resources
258
+ * feat(config): add a language configuration option to the `WebServiceConfiguration` object
259
+ * refactor(handlers): delegate error handling from resource protection to error middleware
260
+ * refactor(handlers): improve 401/404 response handling by routing through exceptions and middleware
261
+ * refactor(handlers): enhance CSRF and origin validation to use error middleware instead of direct responses
262
+ * refactor(handlers): improve service call error handling to raise exceptions and delegate to middleware
263
+ * refactor(handlers): refactor invalid route handler to raise exceptions instead of direct 404 responses
264
+ * fix(types): correct ExpressRequest typedef from `import("express").req` to `import("express").Request`
265
+ * build(config): update publicPath from `packages/web-framework/bin/static` to `bin/static`
266
+ * build(config): update TLS certificate paths to use relative `bin/tls/` paths
267
+ * build(env): update `TI_INSTANCE_CLASS` and `TI_INSTANCE_CONFIG` paths to use relative `bin/` paths
268
+ * build(env): add `TI_LOCALIZATION_LABELS_PATH` environment variable for custom labels
269
+ * build(run): update IDE run configuration to use relative paths and the correct working directory
270
+ * build(labels): add an empty `web-server-labels.json` file for custom localization labels
271
+
272
+ ## Version 1.3.0
273
+
274
+ * feat: first working prototype version
package/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # ti-engine web framework
2
+
3
+ ![Logo](https://raw.githubusercontent.com/Belleal/ti-engine/master/packages/core/docs/ti-engine-icon.ico)
4
+
5
+ Flexible framework for the creation of microservices with node.js.
6
+
7
+ ## Information
8
+
9
+ This is a customizable web framework based on the **ti-engine** framework. Currently under development.
10
+
11
+ ## Configure HTTPS for development
12
+
13
+ Use the `mkcert` tool to create a certificate for development.
14
+
15
+ Step 1: Install the tool:
16
+ ```text
17
+ choco install mkcert
18
+ ```
19
+ Step 2: Install certificate authority:
20
+ ```text
21
+ mkcert -install
22
+ ```
23
+ Step 3: Generate certificate files for localhost:
24
+ ```text
25
+ mkcert localhost 127.0.0.1 ::1
26
+ ```
@@ -0,0 +1,18 @@
1
+ const fs = require( "node:fs" );
2
+ const path = require( "node:path" );
3
+
4
+ function copyLibrary( source, destination ) {
5
+ try {
6
+ fs.mkdirSync( path.dirname( destination ), { recursive: true } );
7
+ fs.copyFileSync( source, destination );
8
+ console.log( `Copied ${ source } -> ${ destination }` );
9
+ } catch ( error ) {
10
+ console.error( `Failed to copy ${ source } -> ${ destination }:`, error.message );
11
+ process.exit( 1 );
12
+ }
13
+ }
14
+
15
+ const staticDir = path.join( __dirname, "..", "static", "scripts", "lib" );
16
+
17
+ copyLibrary( require.resolve( "htmx.org/dist/htmx.min.js" ), path.join( staticDir, "htmx.min.js" ) );
18
+ copyLibrary( require.resolve( "@alpinejs/csp/dist/cdn.min.js" ), path.join( staticDir, "alpinejs-csp.min.js" ) );
@@ -0,0 +1,28 @@
1
+ {
2
+ "interface": {
3
+ "default": {
4
+ "login": {
5
+ "password": {
6
+ "en": "Password",
7
+ "bg": "Парола"
8
+ },
9
+ "sign-in": {
10
+ "en": "Sign In",
11
+ "bg": "Вход"
12
+ },
13
+ "sign-in-azure": {
14
+ "en": "Sign In - Azure",
15
+ "bg": "Вход с Azure"
16
+ },
17
+ "sign-in-google": {
18
+ "en": "Sign In - Google",
19
+ "bg": "Вход с Google"
20
+ },
21
+ "username": {
22
+ "en": "Username",
23
+ "bg": "Потребител"
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "workspace": {
3
+ "root": "packages/web-framework",
4
+ "uuid": "a6c9b71a-41c3-448d-a680-7c1f6c5b42b0"
5
+ }
6
+ }
Binary file
@@ -0,0 +1,21 @@
1
+ <div id="ti-notifications" x-data="tiComponentNotificationBar" class="ti-toast-stack" aria-live="polite" aria-atomic="false">
2
+ <template x-for="n in notifications" x-bind:key="n.id">
3
+ <div class="ti-toast" x-bind:class="n.tone || 'info'" role="alert">
4
+ <div class="ti-toast-bar" aria-hidden="true"></div>
5
+ <div class="ti-toast-icon" aria-hidden="true">
6
+ <span class="ti-icon check md" aria-hidden="true" x-show="n.tone === 'success'"></span>
7
+ <span class="ti-icon close md" aria-hidden="true" x-show="n.tone === 'danger'"></span>
8
+ <span class="ti-icon warning-triangle md" aria-hidden="true" x-show="n.tone === 'warn'"></span>
9
+ <span class="ti-icon info-circle md" aria-hidden="true" x-show="!n.tone || n.tone === 'info'"></span>
10
+ </div>
11
+ <div class="ti-toast-body">
12
+ <div class="ti-toast-title" x-text="n.title || ''"></div>
13
+ <div class="ti-toast-msg" x-text="n.message || ''"></div>
14
+ <div class="ti-toast-details" x-show="n.details" x-text="n.details || ''"></div>
15
+ </div>
16
+ <button class="ti-toast-close" @click="remove(n.id)" aria-label="Dismiss notification" type="button">
17
+ <span class="ti-icon close sm" aria-hidden="true"></span>
18
+ </button>
19
+ </div>
20
+ </template>
21
+ </div>
@@ -0,0 +1,37 @@
1
+ <div x-id="['component-flyout']"
2
+ x-data="tiComponentSidebarFlyout('{ti-config-key}')"
3
+ class="ti-sidebar-flyout-container">
4
+ <!--
5
+ Trigger slot. The placeholder consumer can replace this with any custom button — typically a user avatar
6
+ or app menu trigger. The trigger MUST set x-ref="flyoutButton" and call toggle() to integrate with the
7
+ encapsulated flyout state. If the consumer omits inner content, this default icon button is used.
8
+ -->
9
+ <ti-slot>
10
+ <button x-on:click.stop="toggle()"
11
+ x-on:ti-close-all-flyout="close()"
12
+ x-bind:aria-expanded="isOpen"
13
+ x-bind:aria-controls="$id('component-flyout')"
14
+ x-bind:aria-label="menuTitle"
15
+ x-ref="flyoutButton"
16
+ type="button" class="ti-sidebar-button">
17
+ <span class="ti-icon" x-bind:class="icon" aria-hidden="true"></span>
18
+ </button>
19
+ </ti-slot>
20
+ <div x-on:click.outside="close()"
21
+ x-bind:id="$id('component-flyout')"
22
+ x-show="isOpen"
23
+ x-ref="flyoutPanel"
24
+ role="menu" class="ti-sidebar-flyout">
25
+ <template x-for="(buttonConfig, index) in buttonConfigs">
26
+ <button x-on:click="close()"
27
+ x-bind:hx-get="(!buttonConfig.action.method || buttonConfig.action.method === 'get') ? buttonConfig.action.href : null"
28
+ x-bind:hx-post="(buttonConfig.action.method && buttonConfig.action.method === 'post') ? buttonConfig.action.href : null"
29
+ x-bind:hx-target="buttonConfig.action.target"
30
+ x-bind:hx-swap="buttonConfig.action.swap"
31
+ hx-push-url="true"
32
+ type="button" class="ti-sidebar-flyout-item">
33
+ <span class="ti-icon" x-bind:class="buttonConfig.icon" aria-hidden="true"></span><span x-text="buttonConfig.title"></span>
34
+ </button>
35
+ </template>
36
+ </div>
37
+ </div>
@@ -0,0 +1,33 @@
1
+ <nav class="ti-sidebar" x-data="tiComponentSidebarNav" aria-label="Main navigation">
2
+ <button class="ti-sidebar-collapse-btn" @click="toggleCollapse()" x-bind:aria-label="collapsed ? 'Expand sidebar' : 'Collapse sidebar'" type="button">
3
+ <span class="ti-icon chevron-left xs" aria-hidden="true"></span>
4
+ </button>
5
+
6
+ <!-- Brand slot — override in app-specific frame -->
7
+ <div class="ti-sidebar-brand">
8
+ <div class="ti-sidebar-brand-mark">T</div>
9
+ <div class="ti-sidebar-brand-text-block">
10
+ <span class="ti-sidebar-brand-text">ti-engine</span>
11
+ </div>
12
+ </div>
13
+
14
+ <!-- Navigation slot — override in app-specific frame -->
15
+ <div class="ti-sidebar-nav">
16
+ <a href="/" class="ti-sidebar-item" data-tip="Dashboard" aria-label="Dashboard">
17
+ <span class="ti-sidebar-item-icon">
18
+ <span class="ti-icon dashboard md" aria-hidden="true"></span>
19
+ </span>
20
+ <span class="ti-sidebar-item-label">Dashboard</span>
21
+ </a>
22
+ </div>
23
+
24
+ <!-- Footer -->
25
+ <div class="ti-sidebar-foot">
26
+ <button class="ti-sidebar-item" @click="toggleTheme()" data-tip="Toggle theme" aria-label="Toggle theme" type="button">
27
+ <span class="ti-sidebar-item-icon">
28
+ <span class="ti-icon sun md" aria-hidden="true"></span>
29
+ </span>
30
+ <span class="ti-sidebar-item-label" x-text="theme === 'daylight' ? 'Glass Theme' : 'Daylight Theme'">Toggle Theme</span>
31
+ </button>
32
+ </div>
33
+ </nav>
@@ -0,0 +1,11 @@
1
+ <div x-data="tiComponentTooltip()"
2
+ x-on:mouseover="handleEnter($event)"
3
+ x-on:mouseout="handleLeave($event)"
4
+ x-on:focusin="handleEnter($event)"
5
+ x-on:focusout="handleLeave($event)"
6
+ class="ti-tooltip">
7
+ <div x-show="isVisible"
8
+ x-text="text"
9
+ x-transition
10
+ class="ti-tooltip-bubble"></div>
11
+ </div>
@@ -0,0 +1,6 @@
1
+ <header id="ti-topbar" class="ti-topbar" x-data="tiComponentTopbar">
2
+ <div id="ti-topbar-info">
3
+ <div class="ti-topbar-title" x-text="screenTitle"></div>
4
+ <div class="ti-topbar-sub" x-show="$store.tiApplication.topbarSubtitle" x-text="$store.tiApplication.topbarSubtitle"></div>
5
+ </div>
6
+ </header>
@@ -0,0 +1,3 @@
1
+ <div>
2
+ <span>Administration</span>
3
+ </div>
@@ -0,0 +1,19 @@
1
+ <div class="ti-application" x-data="tiApplication" x-bind:class="{ collapsed: collapsed }">
2
+ <!-- Sidebar -->
3
+ <ti-component-sidebar-placeholder></ti-component-sidebar-placeholder>
4
+
5
+ <!-- Content -->
6
+ <div id="ti-content-wrapper" class="ti-content">
7
+ <!-- Topbar -->
8
+ <ti-component-topbar-placeholder></ti-component-topbar-placeholder>
9
+
10
+ <!-- Application Screen -->
11
+ <div id="ti-content" class="ti-page-scrollable">
12
+ <ti-nested-frame-placeholder hx-get="/app/dashboard" hx-trigger="load" hx-target="this" hx-swap="outerHTML"
13
+ hx-push-url="true"></ti-nested-frame-placeholder>
14
+ </div>
15
+ </div>
16
+
17
+ <!-- Toast notification stack -->
18
+ <ti-component-notification-bar-placeholder></ti-component-notification-bar-placeholder>
19
+ </div>
@@ -0,0 +1,3 @@
1
+ <div>
2
+ <span>Dashboard</span>
3
+ </div>
@@ -0,0 +1,105 @@
1
+ <div id="ti-login" x-data="tiLoginTestUserPanel">
2
+ <div class="ti-login-container">
3
+ <!-- Brand mark -->
4
+ <div class="ti-login-brand">
5
+ <div class="ti-login-brand-mark">T</div>
6
+ <div class="ti-login-brand-text">
7
+ <div class="ti-login-brand-title">Welcome back</div>
8
+ <div class="ti-login-brand-sub">Sign in to continue</div>
9
+ </div>
10
+ </div>
11
+
12
+ <!-- Login card -->
13
+ <div class="ti-login-card">
14
+ <!-- Error message -->
15
+ <div id="ti-error" class="ti-login-error"></div>
16
+
17
+ <!-- Local auth form -->
18
+ <form id="ti-login-form" action="/login/local" method="post" class="ti-login-form">
19
+ <input type="hidden" name="_csrf" value='{ti-csrf-placeholder}'>
20
+ <div>
21
+ <label class="ti-field-label" x-text-label="interface.default.login.username" for="ti-username">Username</label>
22
+ <input id="ti-username"
23
+ name="username"
24
+ autocomplete="username"
25
+ autocapitalize="none"
26
+ spellcheck="false"
27
+ required class="ti-input"
28
+ placeholder="Enter your username"/>
29
+ </div>
30
+ <div>
31
+ <label class="ti-field-label" x-text-label="interface.default.login.password" for="ti-password">Password</label>
32
+ <input id="ti-password"
33
+ name="password"
34
+ type="password"
35
+ autocomplete="current-password"
36
+ enterkeyhint="go"
37
+ required class="ti-input"
38
+ placeholder="Enter your password"/>
39
+ </div>
40
+ <button type="submit" class="ti-btn primary lg full ti-login-submit">
41
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
42
+ stroke-linejoin="round" aria-hidden="true">
43
+ <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4M10 17l5-5-5-5M15 12H3"/>
44
+ </svg>
45
+ <span x-text-label="interface.default.login.sign-in">Sign In</span>
46
+ </button>
47
+ </form>
48
+
49
+ <!-- Divider -->
50
+ <div class="ti-login-divider">
51
+ <div class="ti-login-divider-line"></div>
52
+ <span class="ti-login-divider-label">or continue with</span>
53
+ <div class="ti-login-divider-line"></div>
54
+ </div>
55
+
56
+ <!-- Social auth -->
57
+ <div class="ti-login-social">
58
+ <a href="/login/openid-google" class="ti-btn full">
59
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
60
+ <path d="M21.6 12.23c0-.68-.06-1.33-.17-1.96H12v3.71h5.39a4.6 4.6 0 0 1-2 3.02v2.51h3.23c1.9-1.74 2.98-4.31 2.98-7.28Z" fill="#4285F4"/>
61
+ <path d="M12 22c2.7 0 4.97-.9 6.62-2.42l-3.23-2.51c-.9.6-2.04.96-3.39.96-2.6 0-4.81-1.76-5.6-4.12H3.07v2.6A10 10 0 0 0 12 22Z"
62
+ fill="#34A853"/>
63
+ <path d="M6.4 13.91A6.01 6.01 0 0 1 6.4 12c0-.66.11-1.3.31-1.91V7.49H3.07A10 10 0 0 0 2 12c0 1.61.39 3.14 1.07 4.51l3.33-2.6Z"
64
+ fill="#FBBC05"/>
65
+ <path d="M12 5.97c1.47 0 2.79.5 3.83 1.5l2.87-2.87C16.96 2.99 14.7 2 12 2A10 10 0 0 0 3.07 7.49l3.33 2.6C7.19 7.73 9.4 5.97 12 5.97Z"
66
+ fill="#EA4335"/>
67
+ </svg>
68
+ <span x-text-label="interface.default.login.sign-in-google">Sign in with Google</span>
69
+ </a>
70
+ <a href="/login/openid-azure" class="ti-btn full">
71
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
72
+ <path d="M11.5 2L4 8.5l4 1-4 6.5h8l-2-4 4-1-2.5 8H20L14 2h-2.5Z" fill="#0078D4"/>
73
+ </svg>
74
+ <span x-text-label="interface.default.login.sign-in-azure">Sign in with Azure</span>
75
+ </a>
76
+ </div>
77
+ </div>
78
+
79
+ <!-- TEMPORARY: Test user selector for augmentSession() experimentation. Remove once AD-driven identity is wired up. -->
80
+ <div class="ti-login-test-panel" role="group" aria-label="Test user selector">
81
+ <div class="ti-login-test-panel-title">Test user</div>
82
+ <div class="ti-login-test-panel-sub">Pick an identity to inject on login. Roles are derived from the org chart unless you override them.</div>
83
+ <div class="ti-login-test-panel-pills">
84
+ <template x-for="profile in profiles" :key="profile.employeeID">
85
+ <button type="button"
86
+ class="ti-login-test-pill"
87
+ x-bind:class="{ selected: isSelected(profile) }"
88
+ x-bind:aria-pressed="isSelected(profile)"
89
+ x-on:click="select(profile)">
90
+ <span class="ti-login-test-pill-id" x-text="'#' + profile.employeeID"></span>
91
+ <span class="ti-login-test-pill-roles" x-text="'roles ' + profile.roles.join(', ')"></span>
92
+ </button>
93
+ </template>
94
+ <button type="button"
95
+ class="ti-login-test-pill ghost"
96
+ x-show="selected"
97
+ x-on:click="clear()">Clear</button>
98
+ </div>
99
+ <label class="ti-login-test-panel-override">
100
+ <input type="checkbox" x-model="overrideRoles" x-on:change="onOverrideChanged()"/>
101
+ <span>Override roles (dev) — inject this profile's role list instead of deriving</span>
102
+ </label>
103
+ </div>
104
+ </div>
105
+ </div>
@@ -0,0 +1,3 @@
1
+ <div id="ti-not-found" class="ti-content">
2
+ <span>Not found</span>
3
+ </div>