summaryrefslogtreecommitdiffhomepage
path: root/src/features/workspaces/ui/WorkspacesHome.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-25 10:55:51 +0900
committerAdam Malczewski <[email protected]>2026-06-25 10:55:51 +0900
commit38db3827870960f466be89afbc49f91238d46144 (patch)
tree24cb1b896dfadc31e72552dbe67f00530881242e /src/features/workspaces/ui/WorkspacesHome.svelte
parent17ce47987e673b6618454d033885b17b2a01912e (diff)
downloaddispatch-web-38db3827870960f466be89afbc49f91238d46144.tar.gz
dispatch-web-38db3827870960f466be89afbc49f91238d46144.zip
feat: workspaces shell + cwd-lsp rename + mcp/settings/system-prompt features + app wiring
- workspaces: URL-driven conversation grouping (home listing at /, routing, store, http adapter, WorkspaceCard) wired into the App.svelte shell - rename features/workspace -> features/cwd-lsp (the cwd/lsp status feature) - new features: mcp (status view), settings (chat-limit field), system-prompt (prompt builder), all rendered via the generic surface host - chat: store + ChatView updates - tabs: tabs-store updates - app wiring: ErrorModal (full-screen error surface), app/App.svelte + store.svelte This commit makes HEAD typecheck clean for the first time: the prior HEAD (c95cc77) imported features/settings from app/App.svelte but never committed the feature, so only the full working tree was green.
Diffstat (limited to 'src/features/workspaces/ui/WorkspacesHome.svelte')
-rw-r--r--src/features/workspaces/ui/WorkspacesHome.svelte86
1 files changed, 86 insertions, 0 deletions
diff --git a/src/features/workspaces/ui/WorkspacesHome.svelte b/src/features/workspaces/ui/WorkspacesHome.svelte
new file mode 100644
index 0000000..5894f0a
--- /dev/null
+++ b/src/features/workspaces/ui/WorkspacesHome.svelte
@@ -0,0 +1,86 @@
+<script lang="ts">
+ import { onMount } from "svelte";
+ import type { WorkspaceStore } from "../store.svelte";
+ import { isValidSlug, workspacePath } from "../logic/route";
+ import WorkspaceCard from "./WorkspaceCard.svelte";
+
+ let { store, onNavigate }: { store: WorkspaceStore; onNavigate: (path: string) => void } = $props();
+
+ onMount(() => {
+ void store.refresh();
+ });
+
+ let newSlug = $state("");
+ let slugError = $state<string | null>(null);
+
+ const slugValid = $derived(newSlug.length > 0 && isValidSlug(newSlug));
+
+ function createWorkspace(): void {
+ const slug = newSlug.trim();
+ if (!isValidSlug(slug)) {
+ slugError = "Lowercase letters, digits, and hyphens (1–40 chars).";
+ return;
+ }
+ slugError = null;
+ newSlug = "";
+ onNavigate(workspacePath(slug));
+ }
+</script>
+
+<div class="mx-auto flex h-screen w-full max-w-3xl flex-col gap-4 p-6">
+ <header class="flex items-center justify-between">
+ <h1 class="text-2xl font-bold">Workspaces</h1>
+ <a
+ href="/default"
+ class="btn btn-ghost btn-sm"
+ onclick={(e) => {
+ e.preventDefault();
+ onNavigate("/default");
+ }}
+ >
+ Default
+ </a>
+ </header>
+
+ <form
+ class="flex items-end gap-2"
+ onsubmit={(e) => {
+ e.preventDefault();
+ createWorkspace();
+ }}
+ >
+ <div class="flex-1">
+ <label for="new-ws" class="mb-1 block text-xs font-semibold uppercase opacity-60">New workspace</label>
+ <input
+ id="new-ws"
+ class="input input-bordered w-full"
+ placeholder="my-workspace"
+ bind:value={newSlug}
+ autocomplete="off"
+ spellcheck="false"
+ />
+ </div>
+ <button type="submit" class="btn btn-primary btn-sm" disabled={!slugValid}>Create</button>
+ </form>
+ {#if slugError}
+ <p class="text-xs text-error">{slugError}</p>
+ {/if}
+
+ <div class="flex-1 overflow-y-auto">
+ {#if store.loading && store.list.length === 0}
+ <div class="flex h-32 items-center justify-center">
+ <span class="loading loading-spinner loading-sm opacity-60"></span>
+ </div>
+ {:else if store.list.length === 0}
+ <p class="py-8 text-center text-sm opacity-60">
+ No workspaces yet. Create one above or visit <code>/your-name</code> in the URL.
+ </p>
+ {:else}
+ <ul class="flex flex-col gap-2">
+ {#each store.list as ws (ws.id)}
+ <WorkspaceCard {ws} {store} {onNavigate} />
+ {/each}
+ </ul>
+ {/if}
+ </div>
+</div>