Asagiri 朝霧

widget/ · 28 KB unminified · 7 KB gzipped

Sasagani: capture without breaking flow.

A floating fragment-input that lives in the corner of any page. Paste a URL, type a half-thought, hit "Drop it in" — the fragment lands in the Asagiri inbox thread and you go back to what you were doing. Preact, no framework dependencies, one script tag, ~7 KB on the wire.

Install

Three modes, selected by the data-api attribute:

<!-- Public demo: localStorage only, nothing leaves the browser -->
<script src="https://your-host/sasagani.js"></script>

<!-- Local webapp: hits webapp running on your own machine -->
<script src="..." data-api="http://localhost:3003"></script>

<!-- Gateway (Cloudflare-Access-gated): cross-device, authenticated -->
<script src="..." data-api="https://api.brightraven.world/asagiri"></script>

When data-api is missing or set to "demo", the widget uses a localStorage backend — useful for showcases. When pointed at a real webapp, the bundle sends credentials: 'include' so Cloudflare Access cookies ride along. A 401/403 surfaces a dedicated owner-only notice instead of a generic connection error. The gateway URL (third mode) routes through a single shared api.brightraven.world hostname with Caddy path-prefix dispatch internally; new tools plug in at /<tool>/ without any DNS change.

Try it — live demo

Fully playable. The widget below is the real bundle in demo mode (no data-api set). Drop a URL, type a thought, watch the connections form. Fragments are kept in your browser's localStorage only — refresh the page and they stay, clear site data and they're gone. Nothing is sent anywhere.

The 48×48 floating button appears bottom-right. Drag it to reposition. Click to expand the panel. Try pasting an article URL, then paste another with overlapping wording — a connection line will draw between them in the mini-web visualization.

Captures from a real session

Screenshots from running sasagani against the production Asagiri webapp. The flow is the same as the demo above; the differences are persistence (real SQLite on your machine instead of localStorage) and AI-generated connection descriptions (Gemini summarises why two fragments relate, replacing the demo's naive shared-word heuristic).

Asagiri ritual view in empty state, with the top nav (Ritual, Dashboard, Kanban, Territory, Settings) and a 0-day streak indicator.
Webapp empty state — before sasagani has fed any fragments in.
Dashboard with four KPI cards (Ideas Researched, Day Streak, /15 Domains, Avg Score) and a filterable table of all ideas.
Dashboard — fragments processed by the engine surface as scored ideas.
Opportunity Kanban with an Inbox row and four columns: Interested, Researching, Executing, Passed.
Kanban triage — drag fragments through the funnel.
Territory Map showing fifteen domain pills (AI/ML, Developer Tools, Gaming, FinTech, …) with Uncharted (15) header.
Territory map — which domains the inbox spans.

Behavior

  1. Always docked, never blocking. The button sits in the bottom-right with a small visual footprint. It never overlays content you're reading.
  2. Two-state input. Click expands a small text panel. Click outside, hit Escape, or hit "Drop it in" to collapse.
  3. URL or text. Pasted URLs get URL-classified; free text becomes a note fragment. The widget doesn't try to be clever about classification beyond that — that's downstream's job.
  4. Inbox-thread routing. Every fragment lands in a thread named inbox. The webapp's triage views (kanban, territory) consume from there.
  5. Local-first. The widget only knows one host. No CDN calls, no analytics, no third-party fonts.

Backend contract

The widget calls three endpoints:

API endpoints used by the widget
Method Path Purpose
GET /api/sasagani/threads Lookup or create the active inbox thread
POST /api/sasagani/threads Create inbox thread if none exists
POST /api/sasagani/fragments Submit a fragment (URL or text)

All three are implemented in webapp/src/app/api/sasagani/*. CORS is set there so the widget can be embedded on any origin.

Bundle

PropertyValue
FrameworkPreact (no React runtime)
BuildVite + terser, single-file IIFE
Size (raw)~28 KB
Size (gzipped)~7 KB
DependenciesNone at runtime
Origin / data flowSingle configured host; no telemetry

Why Preact, not vanilla

Two reasons. First, the input UI grew non-trivially — composition states, paste handling, optimistic UI for submission — and hand-written DOM became a maintenance burden. Second, Preact's render cycle scales to future widgets (review queue, daily digest popup) without rewriting the mount logic. Bundle cost stayed under 10 KB gzipped, which is the ceiling we agreed on for "embeddable anywhere".