wsb-daemon 0.275.7622

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the machine
<!--
  A new worktree of one project, made by prj: `prj wt add`, which places every
  repository of the project as a worktree of main-worktree's own.

  The branch and the starting point are optional; prj's defaults are its own
  (`card/<name>`, from the remote's default branch), and the form does not
  guess at them.
-->
<script lang="ts">
  import { actionKey, levels } from "../../stores/levels.svelte.js";

  interface Props {
    project: string;
  }

  let { project }: Props = $props();

  let open = $state(false);
  let name = $state("");
  let branch = $state("");
  let from = $state("");

  const outcome = $derived(levels.outcome(actionKey.add(project)));
  const valid = $derived(/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name.trim()));

  async function submit(e: SubmitEvent) {
    e.preventDefault();
    if (!valid || outcome?.running) return;
    const added = await levels.addWorktree({
      project,
      name: name.trim(),
      ...(branch.trim() ? { branch: branch.trim() } : {}),
      ...(from.trim() ? { from: from.trim() } : {}),
    });
    if (added) {
      name = "";
      branch = "";
      from = "";
      open = false;
    }
  }
</script>

{#if open}
  <form class="new-worktree" onsubmit={submit} aria-label="New worktree of {project}">
    <label class="field">
      <span class="field-label">Name</span>
      <input class="field-input" bind:value={name} placeholder="feature" required />
    </label>
    <label class="field">
      <span class="field-label">Branch (optional)</span>
      <input class="field-input" bind:value={branch} placeholder="card/{name.trim() || 'name'}" />
    </label>
    <label class="field">
      <span class="field-label">From (optional)</span>
      <input class="field-input" bind:value={from} placeholder="origin/HEAD" />
    </label>
    <div class="button-row">
      <button class="primary" type="submit" disabled={!valid || outcome?.running}>
        {outcome?.running ? "Creating…" : "Create worktree"}
      </button>
      <button class="secondary" type="button" onclick={() => (open = false)}>Cancel</button>
    </div>
  </form>
{:else}
  <button class="secondary tiny" onclick={() => (open = true)}>New worktree</button>
{/if}

{#if outcome && !outcome.running}
  <div class="new-worktree-outcome" class:failed={outcome.error !== null}>
    {#if outcome.error !== null}
      <span role="alert">{outcome.error}</span>
    {:else if outcome.output}
      <pre>{outcome.output}</pre>
    {/if}
    <button class="quiet" onclick={() => levels.dismiss(actionKey.add(project))}>Dismiss</button>
  </div>
{/if}

<style>
  .new-worktree {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: var(--wb-spacing-8);
    align-items: end;
    padding: var(--wb-spacing-8) var(--wb-spacing-10);
    border-top: 1px solid var(--vscode-panel-border);
  }

  .new-worktree-outcome {
    display: flex;
    align-items: flex-start;
    gap: var(--wb-spacing-8);
    padding: var(--wb-spacing-4) var(--wb-spacing-10);
    font-size: var(--wb-font-size-11);
    color: var(--vscode-descriptionForeground);
  }

  .new-worktree-outcome.failed {
    color: var(--wb-error);
  }

  .new-worktree-outcome pre {
    margin: 0;
    flex: 1;
    white-space: pre-wrap;
    font-family: var(--vscode-editor-font-family, monospace);
  }

  .new-worktree-outcome span {
    flex: 1;
  }
</style>