feat: Reinitialize frontend with SvelteKit and TypeScript

- Delete old Vite+Svelte frontend
- Initialize new SvelteKit project with TypeScript
- Configure Tailwind CSS v4 + DaisyUI
- Implement JWT authentication with auto-refresh
- Create login page with form validation (Zod)
- Add protected route guards
- Update Docker configuration for single-stage build
- Add E2E tests with Playwright (6/11 passing)
- Fix Svelte 5 reactivity with $state() runes

Known issues:
- 5 E2E tests failing (timing/async issues)
- Token refresh implementation needs debugging
- Validation error display timing
This commit is contained in:
2026-02-17 16:19:59 -05:00
parent 54df6018f5
commit de2d83092e
28274 changed files with 3816354 additions and 90 deletions

View File

@@ -0,0 +1,53 @@
import HTMLElement from '../html-element/HTMLElement.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import HTMLFormElement from '../html-form-element/HTMLFormElement.js';
import Event from '../../event/Event.js';
import HTMLInputElement from '../html-input-element/HTMLInputElement.js';
import HTMLButtonElement from '../html-button-element/HTMLButtonElement.js';
import HTMLMeterElement from '../html-meter-element/HTMLMeterElement.js';
import HTMLOutputElement from '../html-output-element/HTMLOutputElement.js';
import HTMLProgressElement from '../html-progress-element/HTMLProgressElement.js';
import HTMLSelectElement from '../html-select-element/HTMLSelectElement.js';
import HTMLTextAreaElement from '../html-text-area-element/HTMLTextAreaElement.js';
/**
* HTML Label Element.
*
* Reference:
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement.
*/
export default class HTMLLabelElement extends HTMLElement {
cloneNode: (deep?: boolean) => HTMLLabelElement;
/**
* Returns a string containing the ID of the labeled control. This reflects the "for" attribute.
*
* @returns ID of the labeled control.
*/
get htmlFor(): string;
/**
* Sets a string containing the ID of the labeled control. This reflects the "for" attribute.
*
* @param htmlFor ID of the labeled control.
*/
set htmlFor(htmlFor: string);
/**
* Returns an HTML element representing the control with which the label is associated.
*
* @returns Control element.
*/
get control(): HTMLInputElement | HTMLButtonElement | HTMLMeterElement | HTMLOutputElement | HTMLProgressElement | HTMLSelectElement | HTMLTextAreaElement | null;
/**
* Returns the parent form element.
*
* @returns Form.
*/
get form(): HTMLFormElement | null;
/**
* @override
*/
[PropertySymbol.cloneNode](deep?: boolean): HTMLLabelElement;
/**
* @override
*/
dispatchEvent(event: Event): boolean;
}
//# sourceMappingURL=HTMLLabelElement.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"HTMLLabelElement.d.ts","sourceRoot":"","sources":["../../../src/nodes/html-label-element/HTMLLabelElement.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,gCAAgC,CAAC;AACzD,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAC;AAC1D,OAAO,eAAe,MAAM,yCAAyC,CAAC;AACtE,OAAO,KAAK,MAAM,sBAAsB,CAAC;AAEzC,OAAO,gBAAgB,MAAM,2CAA2C,CAAC;AAGzE,OAAO,iBAAiB,MAAM,6CAA6C,CAAC;AAC5E,OAAO,gBAAgB,MAAM,2CAA2C,CAAC;AACzE,OAAO,iBAAiB,MAAM,6CAA6C,CAAC;AAC5E,OAAO,mBAAmB,MAAM,iDAAiD,CAAC;AAClF,OAAO,iBAAiB,MAAM,6CAA6C,CAAC;AAC5E,OAAO,mBAAmB,MAAM,kDAAkD,CAAC;AAEnF;;;;;GAKG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,WAAW;IAEzC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,gBAAgB,CAAC;IAE/D;;;;OAIG;IACH,IAAW,OAAO,IAAI,MAAM,CAM3B;IAED;;;;OAIG;IACH,IAAW,OAAO,CAAC,OAAO,EAAE,MAAM,EAEjC;IAED;;;;OAIG;IACH,IAAW,OAAO,IACf,gBAAgB,GAChB,iBAAiB,GACjB,gBAAgB,GAChB,iBAAiB,GACjB,mBAAmB,GACnB,iBAAiB,GACjB,mBAAmB,GACnB,IAAI,CA4BN;IAED;;;;OAIG;IACH,IAAW,IAAI,IAAI,eAAe,GAAG,IAAI,CAExC;IAED;;OAEG;IACa,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,IAAI,UAAQ,GAAG,gBAAgB;IAI1E;;OAEG;IACa,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;CAkBpD"}

View File

@@ -0,0 +1,94 @@
import HTMLElement from '../html-element/HTMLElement.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import EventPhaseEnum from '../../event/EventPhaseEnum.js';
import MouseEvent from '../../event/events/MouseEvent.js';
/**
* HTML Label Element.
*
* Reference:
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement.
*/
export default class HTMLLabelElement extends HTMLElement {
/**
* Returns a string containing the ID of the labeled control. This reflects the "for" attribute.
*
* @returns ID of the labeled control.
*/
get htmlFor() {
const htmlFor = this.getAttribute('for');
if (htmlFor !== null) {
return htmlFor;
}
return htmlFor !== null ? htmlFor : '';
}
/**
* Sets a string containing the ID of the labeled control. This reflects the "for" attribute.
*
* @param htmlFor ID of the labeled control.
*/
set htmlFor(htmlFor) {
this.setAttribute('for', htmlFor);
}
/**
* Returns an HTML element representing the control with which the label is associated.
*
* @returns Control element.
*/
get control() {
const htmlFor = this.getAttribute('for');
if (htmlFor !== null) {
if (!htmlFor || !this[PropertySymbol.isConnected]) {
return null;
}
const control = (this[PropertySymbol.rootNode].getElementById(htmlFor));
if (control) {
switch (control[PropertySymbol.tagName]) {
case 'INPUT':
return control.type !== 'hidden' ? control : null;
case 'BUTTON':
case 'METER':
case 'OUTPUT':
case 'PROGRESS':
case 'SELECT':
case 'TEXTAREA':
return control;
default:
return null;
}
}
}
return (this.querySelector('button,input:not([type="hidden"]),meter,output,progress,select,textarea'));
}
/**
* Returns the parent form element.
*
* @returns Form.
*/
get form() {
return this.control?.form || null;
}
/**
* @override
*/
[PropertySymbol.cloneNode](deep = false) {
return super[PropertySymbol.cloneNode](deep);
}
/**
* @override
*/
dispatchEvent(event) {
const returnValue = super.dispatchEvent(event);
if (!event[PropertySymbol.defaultPrevented] &&
event.type === 'click' &&
(event.eventPhase === EventPhaseEnum.atTarget ||
event.eventPhase === EventPhaseEnum.bubbling) &&
event instanceof MouseEvent) {
const control = this.control;
if (control && event.target !== control) {
control.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
}
}
return returnValue;
}
}
//# sourceMappingURL=HTMLLabelElement.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"HTMLLabelElement.js","sourceRoot":"","sources":["../../../src/nodes/html-label-element/HTMLLabelElement.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,16 @@
import HTMLElement from '../html-element/HTMLElement.js';
import HTMLLabelElement from './HTMLLabelElement.js';
import NodeList from '../node/NodeList.js';
/**
* Utility for finding labels associated with a form element.
*/
export default class HTMLLabelElementUtility {
/**
* Returns label elements for a form element.
*
* @param element Element to get labels for.
* @returns Label elements.
*/
static getAssociatedLabelElements(element: HTMLElement): NodeList<HTMLLabelElement>;
}
//# sourceMappingURL=HTMLLabelElementUtility.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"HTMLLabelElementUtility.d.ts","sourceRoot":"","sources":["../../../src/nodes/html-label-element/HTMLLabelElementUtility.ts"],"names":[],"mappings":"AACA,OAAO,WAAW,MAAM,gCAAgC,CAAC;AACzD,OAAO,gBAAgB,MAAM,uBAAuB,CAAC;AACrD,OAAO,QAAQ,MAAM,qBAAqB,CAAC;AAI3C;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,uBAAuB;IAC3C;;;;;OAKG;WACW,0BAA0B,CAAC,OAAO,EAAE,WAAW,GAAG,QAAQ,CAAC,gBAAgB,CAAC;CA0B1F"}

View File

@@ -0,0 +1,35 @@
import NodeList from '../node/NodeList.js';
import * as PropertySymbol from '../../PropertySymbol.js';
/**
* Utility for finding labels associated with a form element.
*/
export default class HTMLLabelElementUtility {
/**
* Returns label elements for a form element.
*
* @param element Element to get labels for.
* @returns Label elements.
*/
static getAssociatedLabelElements(element) {
const id = element.id;
let labels;
if (id && element[PropertySymbol.isConnected]) {
const rootNode = element[PropertySymbol.rootNode] ||
element[PropertySymbol.ownerDocument];
labels = (rootNode.querySelectorAll(`label[for="${id}"]`)[PropertySymbol.items]);
}
else {
labels = [];
}
let parent = element[PropertySymbol.parentNode];
while (parent) {
if (parent['tagName'] === 'LABEL') {
labels.push(parent);
break;
}
parent = parent[PropertySymbol.parentNode];
}
return new NodeList(PropertySymbol.illegalConstructor, labels);
}
}
//# sourceMappingURL=HTMLLabelElementUtility.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"HTMLLabelElementUtility.js","sourceRoot":"","sources":["../../../src/nodes/html-label-element/HTMLLabelElementUtility.ts"],"names":[],"mappings":"AAGA,OAAO,QAAQ,MAAM,qBAAqB,CAAC;AAE3C,OAAO,KAAK,cAAc,MAAM,yBAAyB,CAAC;AAE1D;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,uBAAuB;IAC3C;;;;;OAKG;IACI,MAAM,CAAC,0BAA0B,CAAC,OAAoB;QAC5D,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;QACtB,IAAI,MAA0B,CAAC;QAE/B,IAAI,EAAE,IAAI,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/C,MAAM,QAAQ,GACU,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC;gBACvD,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;YACvC,MAAM,GAAuB,CAC5B,QAAQ,CAAC,gBAAgB,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CACrE,CAAC;QACH,CAAC;aAAM,CAAC;YACP,MAAM,GAAG,EAAE,CAAC;QACb,CAAC;QAED,IAAI,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QAChD,OAAO,MAAM,EAAE,CAAC;YACf,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,OAAO,EAAE,CAAC;gBACnC,MAAM,CAAC,IAAI,CAAmB,MAAM,CAAC,CAAC;gBACtC,MAAM;YACP,CAAC;YACD,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QAC5C,CAAC;QAED,OAAO,IAAI,QAAQ,CAAmB,cAAc,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;IAClF,CAAC;CACD"}