feat: Phase 1 — extract the client (web SPA + desktop) to dtoro/oikos-web
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:
2
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/index.d.ts
generated
vendored
Normal file
2
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as TextSelectionLayer } from "./text-selection-layer.svelte";
|
||||
export type { TextSelectionLayerProps } from "./types.js";
|
||||
1
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/index.js
generated
vendored
Normal file
1
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/index.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export { default as TextSelectionLayer } from "./text-selection-layer.svelte";
|
||||
26
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/text-selection-layer.svelte
generated
vendored
Normal file
26
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/text-selection-layer.svelte
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { boxWith } from "svelte-toolbelt";
|
||||
import type { TextSelectionLayerImplProps } from "./types.js";
|
||||
import { TextSelectionLayerState } from "./use-text-selection-layer.svelte.js";
|
||||
import { noop } from "../../../internal/noop.js";
|
||||
|
||||
let {
|
||||
preventOverflowTextSelection = true,
|
||||
onPointerDown = noop,
|
||||
onPointerUp = noop,
|
||||
id,
|
||||
children,
|
||||
enabled,
|
||||
ref,
|
||||
}: TextSelectionLayerImplProps = $props();
|
||||
|
||||
TextSelectionLayerState.create({
|
||||
id: boxWith(() => id),
|
||||
onPointerDown: boxWith(() => onPointerDown),
|
||||
onPointerUp: boxWith(() => onPointerUp),
|
||||
enabled: boxWith(() => enabled && preventOverflowTextSelection),
|
||||
ref,
|
||||
});
|
||||
</script>
|
||||
|
||||
{@render children?.()}
|
||||
4
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/text-selection-layer.svelte.d.ts
generated
vendored
Normal file
4
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/text-selection-layer.svelte.d.ts
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import type { TextSelectionLayerImplProps } from "./types.js";
|
||||
declare const TextSelectionLayer: import("svelte").Component<TextSelectionLayerImplProps, {}, "">;
|
||||
type TextSelectionLayer = ReturnType<typeof TextSelectionLayer>;
|
||||
export default TextSelectionLayer;
|
||||
33
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/types.d.ts
generated
vendored
Normal file
33
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { Snippet } from "svelte";
|
||||
import type { ReadableBox } from "svelte-toolbelt";
|
||||
export type PointerHandler = (e: PointerEvent) => void;
|
||||
export type TextSelectionLayerProps = {
|
||||
/**
|
||||
* Passing `true` will prevent the overflow of text selection
|
||||
* outside the element, provided the element is the top layer.
|
||||
* @defaultValue `true`
|
||||
*/
|
||||
preventOverflowTextSelection?: boolean;
|
||||
};
|
||||
export type TextSelectionLayerImplProps = {
|
||||
/**
|
||||
* Whether the layer is enabled. Currently, we determine this with the
|
||||
* `presence` returned from the `presence` layer.
|
||||
*/
|
||||
enabled: boolean;
|
||||
/**
|
||||
* DOM ID of the node.
|
||||
*/
|
||||
id: string;
|
||||
children?: Snippet;
|
||||
/**
|
||||
* Callback fired when pointerdown event triggers. Call `preventDefault()`
|
||||
* to disable the prevention of text-selection overflow.
|
||||
*/
|
||||
onPointerDown?: PointerHandler;
|
||||
/**
|
||||
* Callback fired when pointerup event triggers.
|
||||
*/
|
||||
onPointerUp?: PointerHandler;
|
||||
ref: ReadableBox<HTMLElement | null>;
|
||||
} & TextSelectionLayerProps;
|
||||
1
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/types.js
generated
vendored
Normal file
1
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/types.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
14
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.d.ts
generated
vendored
Normal file
14
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.d.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import { DOMContext, type ReadableBoxedValues } from "svelte-toolbelt";
|
||||
import type { TextSelectionLayerImplProps } from "./types.js";
|
||||
interface TextSelectionLayerStateOpts extends ReadableBoxedValues<Required<Omit<TextSelectionLayerImplProps, "children" | "preventOverflowTextSelection" | "ref">> & {
|
||||
ref: HTMLElement | null;
|
||||
}> {
|
||||
}
|
||||
export declare class TextSelectionLayerState {
|
||||
#private;
|
||||
static create(opts: TextSelectionLayerStateOpts): TextSelectionLayerState;
|
||||
readonly opts: TextSelectionLayerStateOpts;
|
||||
readonly domContext: DOMContext;
|
||||
constructor(opts: TextSelectionLayerStateOpts);
|
||||
}
|
||||
export {};
|
||||
94
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.js
generated
vendored
Normal file
94
web/node_modules/bits-ui/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.js
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
import { DOMContext, composeHandlers, contains, executeCallbacks, } from "svelte-toolbelt";
|
||||
import { watch } from "runed";
|
||||
import { on } from "svelte/events";
|
||||
import { noop } from "../../../internal/noop.js";
|
||||
import { isHTMLElement } from "../../../internal/is.js";
|
||||
const noopPointer = () => { };
|
||||
globalThis.bitsTextSelectionLayers ??= new Map();
|
||||
export class TextSelectionLayerState {
|
||||
static create(opts) {
|
||||
return new TextSelectionLayerState(opts);
|
||||
}
|
||||
opts;
|
||||
domContext;
|
||||
#unsubSelectionLock = noop;
|
||||
#enabledSnapshot = false;
|
||||
#onPointerDownSnapshot = noopPointer;
|
||||
#onPointerUpSnapshot = noopPointer;
|
||||
constructor(opts) {
|
||||
this.opts = opts;
|
||||
this.domContext = new DOMContext(opts.ref);
|
||||
let unsubEvents = noop;
|
||||
watch(() => [
|
||||
this.opts.enabled.current,
|
||||
this.opts.onPointerDown.current,
|
||||
this.opts.onPointerUp.current,
|
||||
], ([enabled, onPointerDown, onPointerUp]) => {
|
||||
this.#enabledSnapshot = enabled;
|
||||
this.#onPointerDownSnapshot = onPointerDown;
|
||||
this.#onPointerUpSnapshot = onPointerUp;
|
||||
if (enabled) {
|
||||
globalThis.bitsTextSelectionLayers.set(this, this.opts.enabled);
|
||||
unsubEvents();
|
||||
unsubEvents = this.#addEventListeners();
|
||||
}
|
||||
return () => {
|
||||
this.#enabledSnapshot = false;
|
||||
unsubEvents();
|
||||
this.#resetSelectionLock();
|
||||
globalThis.bitsTextSelectionLayers.delete(this);
|
||||
};
|
||||
});
|
||||
}
|
||||
#addEventListeners() {
|
||||
return executeCallbacks(on(this.domContext.getDocument(), "pointerdown", this.#pointerdown), on(this.domContext.getDocument(), "pointerup", composeHandlers(this.#resetSelectionLock, this.#pointerupUserHandler)));
|
||||
}
|
||||
#pointerupUserHandler = (e) => {
|
||||
this.#onPointerUpSnapshot(e);
|
||||
};
|
||||
#pointerdown = (e) => {
|
||||
const node = this.opts.ref.current;
|
||||
const target = e.target;
|
||||
if (!isHTMLElement(node) || !isHTMLElement(target) || !this.#enabledSnapshot)
|
||||
return;
|
||||
/**
|
||||
* We only lock user-selection overflow if layer is the top most layer and
|
||||
* pointerdown occurred inside the node. You are still allowed to select text
|
||||
* outside the node provided pointerdown occurs outside the node.
|
||||
*/
|
||||
if (!isHighestLayer(this) || !contains(node, target))
|
||||
return;
|
||||
this.#onPointerDownSnapshot(e);
|
||||
if (e.defaultPrevented)
|
||||
return;
|
||||
this.#unsubSelectionLock = preventTextSelectionOverflow(node, this.domContext.getDocument().body);
|
||||
};
|
||||
#resetSelectionLock = () => {
|
||||
this.#unsubSelectionLock();
|
||||
this.#unsubSelectionLock = noop;
|
||||
};
|
||||
}
|
||||
const getUserSelect = (node) => node.style.userSelect || node.style.webkitUserSelect;
|
||||
function preventTextSelectionOverflow(node, body) {
|
||||
const originalBodyUserSelect = getUserSelect(body);
|
||||
const originalNodeUserSelect = getUserSelect(node);
|
||||
setUserSelect(body, "none");
|
||||
setUserSelect(node, "text");
|
||||
return () => {
|
||||
setUserSelect(body, originalBodyUserSelect);
|
||||
setUserSelect(node, originalNodeUserSelect);
|
||||
};
|
||||
}
|
||||
function setUserSelect(node, value) {
|
||||
node.style.userSelect = value;
|
||||
node.style.webkitUserSelect = value;
|
||||
}
|
||||
function isHighestLayer(instance) {
|
||||
const layersArr = [...globalThis.bitsTextSelectionLayers];
|
||||
if (!layersArr.length)
|
||||
return false;
|
||||
const highestLayer = layersArr.at(-1);
|
||||
if (!highestLayer)
|
||||
return false;
|
||||
return highestLayer[0] === instance;
|
||||
}
|
||||
Reference in New Issue
Block a user