feat: Phase 1 — extract the client (web SPA + desktop) to dtoro/oikos-web
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled

Problem: the hexagonal refactor churns the backend tree for nine more
phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper,
compose/web image) must move to its own repo first so doc/layout
rewrites land once on a backend-only tree.

Change:
- New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/
  (updateURL repointed to oikos-web releases), compose/, own CI (web +
  desktop jobs), own deploy script (CI-green gate, TOCTOU guard,
  version-tagged images, prune-to-3), own webhook receiver on :9798 +
  launchd unit, own compose project publishing the same 8091:80.
- Cutover executed on mac-mini in order: oikos stack's web service
  stopped+removed, oikos-web project brought up on 8091; outer Caddy
  untouched (targets the published port) — serving + Authentik flow +
  /wails 404 quirk verified post-cutover.
- Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI
  workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install
  targets, the compose web service, oikos-web from deploy.sh's fallback
  prune list; wails + go-keyring dropped from go.mod, vendor synced.
- README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now
  point at the new repo; mbse + mascot design docs carry a path note.

Risk: production SPA serving depends on the new pipeline now; rollback
is versioned-image re-up of the old web service from a pre-split
checkout (port 8091). Desktop builds installed before the split still
check dtoro/oikos releases — one manual reinstall, noted in the
oikos-web release notes.

Verification: go vet, make test (race), make generate-check, golangci
(no new findings; baseline down 400→365); post-cutover curls —
localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
This commit is contained in:
2026-08-15 22:27:52 +02:00
parent e074f04bdf
commit d4d99a7473
18854 changed files with 2615729 additions and 173735 deletions

219
web/node_modules/bits-ui/dist/bits/slider/helpers.js generated vendored Normal file
View File

@@ -0,0 +1,219 @@
export function getRangeStyles(direction, min, max) {
const styles = {
position: "absolute",
};
if (direction === "lr") {
styles.left = `${min}%`;
styles.right = `${max}%`;
}
else if (direction === "rl") {
styles.right = `${min}%`;
styles.left = `${max}%`;
}
else if (direction === "bt") {
styles.bottom = `${min}%`;
styles.top = `${max}%`;
}
else {
styles.top = `${min}%`;
styles.bottom = `${max}%`;
}
return styles;
}
export function getThumbStyles(direction, thumbPos) {
const styles = {
position: "absolute",
};
if (direction === "lr") {
styles.left = `${thumbPos}%`;
styles.translate = "-50% 0";
}
else if (direction === "rl") {
styles.right = `${thumbPos}%`;
styles.translate = "50% 0";
}
else if (direction === "bt") {
styles.bottom = `${thumbPos}%`;
styles.translate = "0 50%";
}
else {
styles.top = `${thumbPos}%`;
styles.translate = "0 -50%";
}
return styles;
}
export function getTickStyles(direction, tickPosition, offsetPercentage) {
const style = {
position: "absolute",
};
if (direction === "lr") {
style.left = `${tickPosition}%`;
style.translate = `${offsetPercentage}% 0`;
}
else if (direction === "rl") {
style.right = `${tickPosition}%`;
style.translate = `${-offsetPercentage}% 0`;
}
else if (direction === "bt") {
style.bottom = `${tickPosition}%`;
style.translate = `0 ${-offsetPercentage}%`;
}
else {
style.top = `${tickPosition}%`;
style.translate = `0 ${offsetPercentage}%`;
}
return style;
}
export function getTickLabelStyles(direction, tickPosition, labelPosition = "top") {
const style = {
position: "absolute",
};
if (direction === "lr" || direction === "rl") {
// Horizontal slider
style.left = direction === "lr" ? `${tickPosition}%` : undefined;
style.right = direction === "rl" ? `${tickPosition}%` : undefined;
style.translate = "-50% 0";
if (labelPosition === "top") {
style.bottom = "100%";
}
else if (labelPosition === "bottom") {
style.top = "100%";
}
}
else {
// Vertical slider - use same positioning as ticks
if (direction === "tb") {
style.top = `${tickPosition}%`;
}
else {
style.bottom = `${tickPosition}%`;
}
style.translate = "0 50%";
if (labelPosition === "left") {
style.right = "100%";
}
else if (labelPosition === "right") {
style.left = "100%";
}
}
return style;
}
export function getThumbLabelStyles(direction, thumbPosition, labelPosition = "top") {
const style = {
position: "absolute",
};
if (direction === "lr" || direction === "rl") {
// Horizontal slider
style.left = direction === "lr" ? `${thumbPosition}%` : undefined;
style.right = direction === "rl" ? `${thumbPosition}%` : undefined;
style.translate = "-50% 0";
if (labelPosition === "top") {
style.bottom = "100%";
}
else if (labelPosition === "bottom") {
style.top = "100%";
}
}
else {
// Vertical slider
if (direction === "tb") {
style.top = `${thumbPosition}%`;
}
else {
style.bottom = `${thumbPosition}%`;
}
style.translate = "0 -50%";
if (labelPosition === "left") {
style.right = "100%";
}
else if (labelPosition === "right") {
style.left = "100%";
}
}
return style;
}
/**
* Gets the number of decimal places in a number
*/
function getDecimalPlaces(num) {
if (Math.floor(num) === num)
return 0;
const str = num.toString();
if (str.indexOf(".") !== -1 && str.indexOf("e-") === -1) {
return str.split(".")[1].length;
}
else if (str.indexOf("e-") !== -1) {
const parts = str.split("e-");
return parseInt(parts[1], 10);
}
return 0;
}
/**
* Rounds a number to the specified number of decimal places
*/
function roundToPrecision(num, precision) {
const factor = Math.pow(10, precision);
return Math.round(num * factor) / factor;
}
/**
* Normalizes step to always be a sorted array of valid values within min/max range
*/
export function normalizeSteps(step, min, max) {
if (typeof step === "number") {
// generate regular steps - match original behavior exactly
const difference = max - min;
let count = Math.ceil(difference / step);
// Get precision from step to avoid floating point errors
const precision = getDecimalPlaces(step);
// Check if difference is divisible by step using integer arithmetic to avoid floating point errors
const factor = Math.pow(10, precision);
const intDifference = Math.round(difference * factor);
const intStep = Math.round(step * factor);
if (intDifference % intStep === 0) {
count++;
}
const steps = [];
for (let i = 0; i < count; i++) {
const value = min + i * step;
// Round to the precision of the step to avoid floating point errors
const roundedValue = roundToPrecision(value, precision);
steps.push(roundedValue);
}
return steps;
}
return [...new Set(step)].filter((value) => value >= min && value <= max).sort((a, b) => a - b);
}
/**
* Snaps a value to the nearest step in a custom steps array
*/
export function snapValueToCustomSteps(value, steps) {
if (steps.length === 0)
return value;
// Find the closest step
let closest = steps[0];
let minDistance = Math.abs(value - closest);
for (const step of steps) {
const distance = Math.abs(value - step);
if (distance < minDistance) {
minDistance = distance;
closest = step;
}
}
return closest;
}
/**
* Gets the next/previous step value for keyboard navigation
*/
export function getAdjacentStepValue(currentValue, steps, direction) {
const currentIndex = steps.indexOf(currentValue);
if (currentIndex === -1) {
// current value is not in steps, snap to nearest
return snapValueToCustomSteps(currentValue, steps);
}
if (direction === "next") {
return currentIndex < steps.length - 1 ? steps[currentIndex + 1] : currentValue;
}
else {
return currentIndex > 0 ? steps[currentIndex - 1] : currentValue;
}
}