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,103 @@
import { isKeyOf, throwParseError } from "@ark/util";
import { InternalPrimitiveConstraint } from "../constraint.js";
export class BaseRange extends InternalPrimitiveConstraint {
boundOperandKind = operandKindsByBoundKind[this.kind];
compiledActual = this.boundOperandKind === "value" ? `data`
: this.boundOperandKind === "length" ? `data.length`
: `data.valueOf()`;
comparator = compileComparator(this.kind, this.exclusive);
numericLimit = this.rule.valueOf();
expression = `${this.comparator} ${this.rule}`;
compiledCondition = `${this.compiledActual} ${this.comparator} ${this.numericLimit}`;
compiledNegation = `${this.compiledActual} ${negatedComparators[this.comparator]} ${this.numericLimit}`;
// we need to compute stringLimit before errorContext, which references it
// transitively through description for date bounds
stringLimit = this.boundOperandKind === "date" ?
dateLimitToString(this.numericLimit)
: `${this.numericLimit}`;
limitKind = this.comparator["0"] === "<" ? "upper" : "lower";
isStricterThan(r) {
const thisLimitIsStricter = this.limitKind === "upper" ?
this.numericLimit < r.numericLimit
: this.numericLimit > r.numericLimit;
return (thisLimitIsStricter ||
(this.numericLimit === r.numericLimit &&
this.exclusive === true &&
!r.exclusive));
}
overlapsRange(r) {
if (this.isStricterThan(r))
return false;
if (this.numericLimit === r.numericLimit && (this.exclusive || r.exclusive))
return false;
return true;
}
overlapIsUnit(r) {
return (this.numericLimit === r.numericLimit && !this.exclusive && !r.exclusive);
}
}
const negatedComparators = {
"<": ">=",
"<=": ">",
">": "<=",
">=": "<"
};
export const boundKindPairsByLower = {
min: "max",
minLength: "maxLength",
after: "before"
};
export const parseExclusiveKey = {
// omit key with value false since it is the default
parse: (flag) => flag || undefined
};
export const createLengthSchemaNormalizer = (kind) => (schema) => {
if (typeof schema === "number")
return { rule: schema };
const { exclusive, ...normalized } = schema;
return exclusive ?
{
...normalized,
rule: kind === "minLength" ? normalized.rule + 1 : normalized.rule - 1
}
: normalized;
};
export const createDateSchemaNormalizer = (kind) => (schema) => {
if (typeof schema === "number" ||
typeof schema === "string" ||
schema instanceof Date)
return { rule: schema };
const { exclusive, ...normalized } = schema;
if (!exclusive)
return normalized;
const numericLimit = typeof normalized.rule === "number" ? normalized.rule
: typeof normalized.rule === "string" ?
new Date(normalized.rule).valueOf()
: normalized.rule.valueOf();
return exclusive ?
{
...normalized,
rule: kind === "after" ? numericLimit + 1 : numericLimit - 1
}
: normalized;
};
export const parseDateLimit = (limit) => typeof limit === "string" || typeof limit === "number" ?
new Date(limit)
: limit;
export const writeInvalidLengthBoundMessage = (kind, limit) => `${kind} bound must be a positive integer (was ${limit})`;
export const createLengthRuleParser = (kind) => (limit) => {
if (!Number.isInteger(limit) || limit < 0)
throwParseError(writeInvalidLengthBoundMessage(kind, limit));
return limit;
};
const operandKindsByBoundKind = {
min: "value",
max: "value",
minLength: "length",
maxLength: "length",
after: "date",
before: "date"
};
export const compileComparator = (kind, exclusive) => `${isKeyOf(kind, boundKindPairsByLower) ? ">" : "<"}${exclusive ? "" : "="}`;
export const dateLimitToString = (limit) => typeof limit === "string" ? limit : new Date(limit).toLocaleString();
export const writeUnboundableMessage = (root) => `Bounded expression ${root} must be exactly one of number, string, Array, or Date`;