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

View File

@@ -0,0 +1,41 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { ComboboxInputProps } from "../types.js";
import { useId } from "../../../internal/use-id.js";
import { FloatingLayer } from "../../utilities/floating-layer/index.js";
import { SelectInputState } from "../../select/select.svelte.js";
let {
id = useId(),
ref = $bindable(null),
child,
defaultValue,
clearOnDeselect = false,
...restProps
}: ComboboxInputProps = $props();
const inputState = SelectInputState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
clearOnDeselect: boxWith(() => clearOnDeselect),
});
if (defaultValue) {
inputState.root.opts.inputValue.current = defaultValue;
}
const mergedProps = $derived(
mergeProps(restProps, inputState.props, { value: inputState.root.opts.inputValue.current })
);
</script>
<FloatingLayer.Anchor {id} ref={inputState.opts.ref}>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<input {...mergedProps} />
{/if}
</FloatingLayer.Anchor>

View File

@@ -0,0 +1,4 @@
import type { ComboboxInputProps } from "../types.js";
declare const ComboboxInput: import("svelte").Component<ComboboxInputProps, {}, "ref">;
type ComboboxInput = ReturnType<typeof ComboboxInput>;
export default ComboboxInput;

View File

@@ -0,0 +1,33 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { ComboboxTriggerProps } from "../types.js";
import { useId } from "../../../internal/use-id.js";
import { SelectComboTriggerState } from "../../select/select.svelte.js";
let {
id = useId(),
ref = $bindable(null),
child,
children,
type = "button",
...restProps
}: ComboboxTriggerProps = $props();
const triggerState = SelectComboTriggerState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, triggerState.props, { type }));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<button {...mergedProps}>
{@render children?.()}
</button>
{/if}

View File

@@ -0,0 +1,4 @@
import type { ComboboxTriggerProps } from "../types.js";
declare const ComboboxTrigger: import("svelte").Component<ComboboxTriggerProps, {}, "ref">;
type ComboboxTrigger = ReturnType<typeof ComboboxTrigger>;
export default ComboboxTrigger;

View File

@@ -0,0 +1,86 @@
<script lang="ts">
import { type WritableBox, boxWith } from "svelte-toolbelt";
import type { ComboboxRootProps } from "../types.js";
import { noop } from "../../../internal/noop.js";
import FloatingLayer from "../../utilities/floating-layer/components/floating-layer.svelte";
import { SelectRootState } from "../../select/select.svelte.js";
import ListboxHiddenInput from "../../select/components/select-hidden-input.svelte";
import { watch } from "runed";
let {
value = $bindable(),
onValueChange = noop,
name = "",
disabled = false,
type,
open = $bindable(false),
onOpenChange = noop,
onOpenChangeComplete = noop,
loop = false,
scrollAlignment = "nearest",
required = false,
items = [],
allowDeselect = true,
inputValue = "",
children,
}: ComboboxRootProps = $props();
if (value === undefined) {
const defaultValue = type === "single" ? "" : [];
value = defaultValue;
}
watch.pre(
() => value,
() => {
if (value !== undefined) return;
value = type === "single" ? "" : [];
}
);
const rootState = SelectRootState.create({
type,
value: boxWith(
() => value!,
(v) => {
value = v;
// @ts-expect-error - we know
onValueChange(v);
}
) as WritableBox<string> | WritableBox<string[]>,
disabled: boxWith(() => disabled),
required: boxWith(() => required),
open: boxWith(
() => open,
(v) => {
open = v;
onOpenChange(v);
}
),
loop: boxWith(() => loop),
scrollAlignment: boxWith(() => scrollAlignment),
name: boxWith(() => name),
isCombobox: true,
items: boxWith(() => items),
allowDeselect: boxWith(() => allowDeselect),
inputValue: boxWith(
() => inputValue,
(v) => (inputValue = v)
),
onOpenChangeComplete: boxWith(() => onOpenChangeComplete),
});
</script>
<FloatingLayer>
{@render children?.()}
</FloatingLayer>
{#if Array.isArray(rootState.opts.value.current)}
{#if rootState.opts.value.current.length}
{#each rootState.opts.value.current as item (item)}
<ListboxHiddenInput value={item} />
{/each}
{/if}
{:else}
<ListboxHiddenInput bind:value={rootState.opts.value.current as string} />
{/if}

View File

@@ -0,0 +1,3 @@
declare const Combobox: import("svelte").Component<import("../types.js").ComboboxRootPropsWithoutHTML, {}, "value" | "open">;
type Combobox = ReturnType<typeof Combobox>;
export default Combobox;

View File

@@ -0,0 +1,15 @@
export { default as Root } from "./components/combobox.svelte";
export { default as Input } from "./components/combobox-input.svelte";
export { default as Separator } from "../separator/components/separator.svelte";
export { default as Arrow } from "../utilities/arrow/arrow.svelte";
export { default as Trigger } from "./components/combobox-trigger.svelte";
export { default as Portal } from "../utilities/portal/portal.svelte";
export { default as Content } from "../select/components/select-content.svelte";
export { default as ContentStatic } from "../select/components/select-content-static.svelte";
export { default as Item } from "../select/components/select-item.svelte";
export { default as Group } from "../select/components/select-group.svelte";
export { default as GroupHeading } from "../select/components/select-group-heading.svelte";
export { default as Viewport } from "../select/components/select-viewport.svelte";
export { default as ScrollDownButton } from "../select/components/select-scroll-down-button.svelte";
export { default as ScrollUpButton } from "../select/components/select-scroll-up-button.svelte";
export type { ComboboxRootProps as RootProps, ComboboxContentProps as ContentProps, ComboboxContentStaticProps as ContentStaticProps, ComboboxInputProps as InputProps, ComboboxItemProps as ItemProps, ComboboxGroupProps as GroupProps, ComboboxGroupHeadingProps as GroupHeadingProps, ComboboxPortalProps as PortalProps, ComboboxArrowProps as ArrowProps, ComboboxTriggerProps as TriggerProps, ComboboxScrollDownButtonProps as ScrollDownButtonProps, ComboboxScrollUpButtonProps as ScrollUpButtonProps, ComboboxViewportProps as ViewportProps, } from "./types.js";

14
web/node_modules/bits-ui/dist/bits/combobox/exports.js generated vendored Normal file
View File

@@ -0,0 +1,14 @@
export { default as Root } from "./components/combobox.svelte";
export { default as Input } from "./components/combobox-input.svelte";
export { default as Separator } from "../separator/components/separator.svelte";
export { default as Arrow } from "../utilities/arrow/arrow.svelte";
export { default as Trigger } from "./components/combobox-trigger.svelte";
export { default as Portal } from "../utilities/portal/portal.svelte";
export { default as Content } from "../select/components/select-content.svelte";
export { default as ContentStatic } from "../select/components/select-content-static.svelte";
export { default as Item } from "../select/components/select-item.svelte";
export { default as Group } from "../select/components/select-group.svelte";
export { default as GroupHeading } from "../select/components/select-group-heading.svelte";
export { default as Viewport } from "../select/components/select-viewport.svelte";
export { default as ScrollDownButton } from "../select/components/select-scroll-down-button.svelte";
export { default as ScrollUpButton } from "../select/components/select-scroll-up-button.svelte";

View File

@@ -0,0 +1 @@
export * as Combobox from "./exports.js";

1
web/node_modules/bits-ui/dist/bits/combobox/index.js generated vendored Normal file
View File

@@ -0,0 +1 @@
export * as Combobox from "./exports.js";

34
web/node_modules/bits-ui/dist/bits/combobox/types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,34 @@
import type { BitsPrimitiveInputAttributes } from "../../shared/attributes.js";
import type { SelectBaseRootPropsWithoutHTML, SelectMultipleRootPropsWithoutHTML, SelectSingleRootPropsWithoutHTML } from "../select/types.js";
import type { WithChild, Without } from "../../internal/types.js";
export type ComboboxBaseRootPropsWithoutHTML = Omit<SelectBaseRootPropsWithoutHTML, "autocomplete"> & {
/**
* A read-only value that can be used to programmatically
* update the input value.
*
* This is useful for updating the displayed label/input
* when the value changes outside of Bits UI.
*/
inputValue?: string;
};
export type ComboboxSingleRootPropsWithoutHTML = ComboboxBaseRootPropsWithoutHTML & SelectSingleRootPropsWithoutHTML;
export type ComboboxSingleRootProps = ComboboxSingleRootPropsWithoutHTML;
export type ComboboxMultipleRootPropsWithoutHTML = ComboboxBaseRootPropsWithoutHTML & SelectMultipleRootPropsWithoutHTML;
export type ComboboxMultipleRootProps = ComboboxMultipleRootPropsWithoutHTML;
export type ComboboxRootPropsWithoutHTML = ComboboxBaseRootPropsWithoutHTML & (ComboboxSingleRootPropsWithoutHTML | ComboboxMultipleRootPropsWithoutHTML);
export type ComboboxRootProps = ComboboxRootPropsWithoutHTML;
export type { SelectContentProps as ComboboxContentProps, SelectContentPropsWithoutHTML as ComboboxContentPropsWithoutHTML, SelectContentStaticProps as ComboboxContentStaticProps, SelectContentStaticPropsWithoutHTML as ComboboxContentStaticPropsWithoutHTML, SelectItemProps as ComboboxItemProps, SelectItemPropsWithoutHTML as ComboboxItemPropsWithoutHTML, SelectItemSnippetProps as ComboboxItemSnippetProps, SelectTriggerProps as ComboboxTriggerProps, SelectTriggerPropsWithoutHTML as ComboboxTriggerPropsWithoutHTML, SelectGroupPropsWithoutHTML as ComboboxGroupPropsWithoutHTML, SelectGroupProps as ComboboxGroupProps, SelectGroupHeadingPropsWithoutHTML as ComboboxGroupHeadingPropsWithoutHTML, SelectGroupHeadingProps as ComboboxGroupHeadingProps, SelectViewportPropsWithoutHTML as ComboboxViewportPropsWithoutHTML, SelectViewportProps as ComboboxViewportProps, SelectScrollDownButtonProps as ComboboxScrollDownButtonProps, SelectScrollDownButtonPropsWithoutHTML as ComboboxScrollDownButtonPropsWithoutHTML, SelectScrollUpButtonProps as ComboboxScrollUpButtonProps, SelectScrollUpButtonPropsWithoutHTML as ComboboxScrollUpButtonPropsWithoutHTML, SelectArrowProps as ComboboxArrowProps, SelectArrowPropsWithoutHTML as ComboboxArrowPropsWithoutHTML, SelectPortalProps as ComboboxPortalProps, SelectPortalPropsWithoutHTML as ComboboxPortalPropsWithoutHTML, } from "../select/types.js";
export type ComboboxInputPropsWithoutHTML = WithChild<{
/**
* The default value of the input. This is not a reactive prop and is only used to populate
* the input when the combobox is first mounted if there is already a value set.
*/
defaultValue?: string;
/**
* Whether to clear the input when the last item is deselected.
*
* @default false
*/
clearOnDeselect?: boolean;
}>;
export type ComboboxInputProps = ComboboxInputPropsWithoutHTML & Without<Omit<BitsPrimitiveInputAttributes, "value">, ComboboxInputPropsWithoutHTML>;

1
web/node_modules/bits-ui/dist/bits/combobox/types.js generated vendored Normal file
View File

@@ -0,0 +1 @@
export {};