saniti-vue

A Vue 3 component library.
10,671
Install
npm install saniti-vue
Latest Version:0.1.67
Vue:3
License:MIT
Last Updated:Sep 14, 2026
Links: npm
Maintainer: nvmsy

saniti-vue

A Vue 3 component library. Components ship as ESM source and are compiled by the consumer's Vite/Vue toolchain.

usage

import "saniti-vue/style.css"
import { Button } from "saniti-vue"

development

npm install
npm run dev

npm run dev opens the Histoire showcase. Stories live in stories/*.story.vue, components in src/components/, tokens in src/style.css.

FadeLabel

<FadeLabel :text="title" :fade="28" />

A single-line label that fades out at its clipped edge instead of ending in an ellipsis. Hovering its parent glides the text to its end, slowing to a stop there, and leaving glides it back. fade is the width of the fade in pixels. SideNav uses it for every row label.

SideNav

<SideNav :sections="sections" :active-key="active" :collapsed="false" @select="active = $event.key" @toggle="onToggle" />

sections is an array of { title?, hint?, clickable?, items }; hint shows as a tooltip on the title, and a clickable title is a button that emits title with its section. Each item accepts:

field type meaning
key string identity, matched against activeKey
label string row text, fades where clipped and glides to its end on hover (see FadeLabel)
icon string iconify name, overridable through the icon slot
badge string, number trailing badge
href string renders the row as a link
active boolean forces the active state regardless of activeKey
children item[] makes the row a collapsible group
defaultOpen boolean group opens the first time it appears
selectable boolean clicking a group row emits select for the row; only its caret toggles

A group opens automatically when it contains the active item. Open state is seeded once per group, so a collapse the user made survives later changes to sections.

Events: select with the clicked item, toggle with { item, open }. Every row element carries data-key, so a host can find the row under a pointer event (a context menu, for instance).

Slots: icon (scoped { item }) replaces the row icon, for children too; header and footer (scoped { collapsed }) render above and below the sections.

Completions

<Completions :items="items" v-model="index" @pick="apply" />

An anchored list of completions for what the user is typing (files after @, commands after /). The host owns the input and the keyboard: it filters items, moves v-model (the highlighted index) on arrow keys, and calls the picked item on enter. The list glides the highlight between rows and keeps the highlighted row scrolled into view.

Items accept id, label, meta, icon, and hits (indices into label to render bold). emptyText renders when there are no items. Give the wrapper a max-height; the list scrolls inside it.

Lightbox

<Lightbox v-model:open="open" v-model:index="index" :images="images" />

A full-window image viewer. images is an array of { src, label? }. The current image fits the window; click or scroll zooms around the pointer, drag pans when zoomed, 0 refits. Left and right arrows (and the edge buttons) move through the list, escape or the backdrop closes. The bar shows the label, natural size, zoom, and position in the list.

StageTrack

<StageTrack :stages="stages" :current="index" label="Compacting" done-label="Compacted" :failed="stopped" fail-label="Cancelled" />

A segmented progress track for work that moves through named stages. stages are the labels, current is the index in progress (equal to stages.length when everything is done, which shows doneLabel). failed freezes the track at the current stage, colours that segment as an error, and shows failLabel instead of the stage name.

ActivityTrail steps may carry thought: true: such a step is listed like the others but left out of the tool count and timing. The footer slot adds controls beside Show all and remains visible when every step fits.

Exchange

<Exchange :turns="turns" proposer-label="Proposer" reviewer-label="Reviewer">
  <template #text="{ turn }"><Markdown :source="turn.text" /></template>
</Exchange>

A two-party deliberation laid out as a back-and-forth. turns is an array of { role, side?, label?, round?, text?, steps?, active?, interrupted?, parallelGroup? } where role: "synthesis" identifies a full-width closing turn. Other roles use side: "left" | "right" and label for placement and captions. Legacy proposer and reviewer roles retain their default placement and labels. Proposer turns sit on the left and reviewer turns on the right, each capped at 78% width, with a mono caption ("Reviewer, round 2"), an ActivityTrail when the turn has steps (active while active is set), and the text in a bubble. Contiguous non-synthesis turns with the same nonempty parallelGroup string share one row while retaining their left or right slot. Ungrouped turns remain on separate rows. A turn with steps and no text is in progress and shows only its trail. A synthesis turn spans the full width under a "Synthesis" divider and renders its text plain. interrupted appends "(interrupted)" to the caption. The text scoped slot receives { turn } so a host can render markdown; without it the text is rendered as is.

TaskBoard

<TaskBoard v-model:selection="selection" :tasks="tasks" :stages="stages" :can-move="canMove" @create="create" @move="move" @open-session="open" />

A vertical list of tasks grouped by stage. stages is [{ key, label, agent }] (TASK_STAGES gives Backlog, Ready, In progress, Review, Done, with an implementing agent on In progress); the order of tasks is the order inside each group. A task is { id, title, prompt, status, model, worktree, attachments, run } where status is a stage key and worktree says whether its sessions run in their own worktree (a row shows a branch mark when they do).

A stage may own an agent: { model, prompt, concurrency, then }. When a task enters a stage with an agent the app queues it there and starts a session when the stage has room; the agent receives the stage prompt together with the task's title, prompt, and attachments. model null inherits the default; then is the stage the task moves to when the run finishes, or null to stay for a human. A stage with agent: null is handled by people. The heading of a stage with an agent shows running / concurrency and the queued count. stageAgentDefaults() gives { model: null, prompt: "", concurrency: 1, then: null }.

run is null until something has run and then { stage, state, note, session, startedAt, endedAt, turns }: stage is the key of the stage whose agent ran; state is one of queued, running, attention, done, failed, stopped; note is the live one-line state; session identifies the session the row's arrow opens; startedAt and endedAt are epoch milliseconds (endedAt stays null while live) and give the elapsed time, which ticks every second while running; turns is the model-call count. isLive(run) is true for queued, running, and attention.

Selection is one model for both kinds of thing on the board: { kind: "task", id } or { kind: "stage", id } (a stage key), or null. Clicking a row selects the task, clicking a stage heading selects the stage, clicking empty space clears it. select emits the same value.

Below 560px of board width rows drop the note and model badge. Rows drag to reorder and to move between groups; the lifted row snaps into the gap nearest the pointer, Escape cancels. canMove(task, toStatus) returns true or a string naming why the move is refused; a refused group shows the reason in its heading while the row hovers it and the drop snaps back. Alt+Up and Alt+Down move the focused row the same way from the keyboard, Up/Down/Home/End walk rows, Enter selects, Ctrl+N / Ctrl+P mirror Down and Up.

Events: select, create with { status } from a group's plus button, move with { task, from, to, index } where index is the position inside to after the task is taken out, and open-session with the task. moveTask(tasks, { id, to, index }) applies a move to a task array and returns the new array (index: null appends). The board never changes tasks itself.

StageEditor

<StageEditor v-model="stage" :stages="stages" :tasks="tasks" :models="models" default-model="claude-sonnet-5" />

The editing area for one stage, meant for the same pane the task editor uses: the label, an Agent switch, and, while the agent is on, its prompt, model (optional, placeholder is the default model), concurrency (capped by maxConcurrency), and what happens when a run finishes (stay, or move to another stage). Turning the agent off keeps its settings so turning it back on restores them. With tasks the switch row shows the stage's live running / concurrency and queued counts. Emits a fresh clone on every change and reconciles external replacements.

TaskEditor

<TaskEditor v-model="task" :stages="stages" :models="models" default-model="claude-sonnet-5" :can-move="canMove" @move="move" @attach="attach" @detach="detach" @stop="stop" @reset="reset" @delete="remove" @open-session="open" />

The editing area for one task: title, prompt, attachments, status, model, and a Worktree switch, then the run when there is one. It emits a fresh clone on every change and reconciles external replacements, so the host owns persistence. models is [{ id, label, provider }]. A task's model overrides the model of every stage agent that runs it; with none chosen the field's placeholder shows the model the task's current stage would use (effectiveModel(task, stage, defaultModel)). Changing the status emits move with { task, from, to, index: null }; statuses that canMove refuses are disabled.

While the run is running or attention the task is locked: title and prompt turn read-only, model and worktree disable, attachments lose their remove crosses and the Attach button, drops are ignored, and Delete is hidden; Stop unlocks it. A queued task stays editable. Files dropped anywhere on the editor or picked through Attach are emitted as attach with a File[]; removing one emits detach with the attachment. The editor never edits task.attachments. Attachments are { id, name, kind, src?, size?, pending?, error? }: every kind renders as a 32px chip, an image with a src showing a small thumbnail in place of the kind icon, pending shows a spinner, error shows the text in red. accept passes through to the file picker.

The run block shows the state, the note, and a mono line naming the stage agent, the turns, and the elapsed time; Session emits open-session, Stop emits stop while the run is live (a queued run leaves the queue), Reset emits reset once stopped, failed, or done so the app can queue another attempt. Delete is two clicks: a ghost Delete arms it, then a red Delete beside a Cancel emits delete; the arm clears when the task changes. errors is { title?, prompt? }. A task with an empty title focuses the title field on arrival; focus() is exposed.

Version History

Version Released License
0.1.67 MIT
0.1.66 MIT
0.1.65 MIT
0.1.64 MIT
0.1.63 MIT