Compare commits

..
19 Commits
Author SHA1 Message Date
archipelagoandClaude Fable 5 1587853ce2 fix(banner): console banner shows the reachable LAN address, not the WG tunnel IP
The welcome banner picked its address with 'hostname -I | awk {print $1}',
so a node with WireGuard up advertised 10.44.0.1 — its own tunnel address,
present on EVERY node — as its web ui / ssh address. Off-tunnel that is
unreachable, and after a headless box moves to a new network it is exactly
the wrong thing to trust (framework-pt, 2026-08-15).

- Pick the default route's source address; fall back to the first address
  that is not WireGuard 10.44/16, CGNAT 100.64/10, or loopback.
- Also print http://<hostname>.local when avahi is up — the one address
  that survives any DHCP change, which is the real answer for headless
  boxes that move between networks.
- scripts/welcome-banner.sh is the new canonical copy, embedded in the
  binary (tor-helper pattern): bootstrap::run_welcome_banner_sync rewrites
  /etc/profile.d/archipelago.sh on ISO-installed nodes at startup, so the
  fix reaches the deployed fleet with the next OTA instead of only fresh
  ISOs. Machines without an installer-baked banner are left untouched.
- Same fix inlined in the live ISO builder's PROFILE heredoc
  (image-recipe/_archived/build-auto-installer-iso.sh).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 08:37:13 -04:00
archipelagoandClaude Fable 5 56d6396142 feat(appgate): gate login gets the real dashboard badge + glass-button states
Demo images / Build & push demo images (push) Successful in 3m0s
- The badge is now the dashboard login's AnimatedLogo, square for square:
  inline SVG (20 white rects, 100ms stagger, 3s loop) inside the same
  gradient ring. The old <img> of favico-black-v2.svg baked a second ring
  into the ring and couldn't animate; the asset leaves the gate allowlist
  since nothing references it now.
- The submit button is .glass-button longhand: hover lift + lightening +
  rim glow, active press, disabled dim — the flat darken-only hover read
  as broken next to /login.
- Loading state: submitting flips the button to spinner + 'Signing in…'/
  'Verifying…' and disables it, via a single inline script admitted by
  CSP sha256 hash (not unsafe-inline; injected markup stays inert, and
  the page still works as a plain POST without JS).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 08:24:27 -04:00
archipelagoandClaude Fable 5 e0d8b9de74 fix(ui): login badge back to the original — gloss stays screensaver/intro only
The Kammergut gloss v3 opt-in had been applied to the /login badge as
well; per operator the glossed disc belongs ONLY on the screensaver and
the onboarding intro (and the splash tap-logo that fronts them). The
login page returns to the plain gradient-ring badge it always had, same
as the dashboard sidebar fix before it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 08:24:19 -04:00
archipelagoandClaude Fable 5 d422218c6b chore(demo): rebuild bundled AIUI with the Routstr provider picker
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 13:41:17 -04:00
archipelagoandClaude Fable 5 a4be1b4b7d feat(aiui): Routstr tops the model picker with the full live catalog
New 'Routstr (sats)' category sits FIRST in the model dropdown
(operator request 2026-08-14), listing every model the node's
/aiui/api/routstr/models proxy returns (432 live today) — the picker
panel now scrolls (max-h 70vh) instead of overflowing. Selecting a
Routstr model routes the turn through the node's paid completions
proxy, and the explicit choice wins even when AIUI runs embedded in
Archy — a selection, not a fallback. Node refusals (no budget armed,
budget spent, wallet can't fund) surface verbatim in chat.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 13:41:17 -04:00
archipelagoandClaude Fable 5 e3bd340725 feat(aiui): Routstr as an explicit, session-gated AI provider path
The D-04 Routstr leg was fallback-only — never user-selectable, and its
Nostr discovery parses a docs-shaped event content ({endpoints, models,
pricing}) that live kind-38421 announcements don't actually carry
({name, about}), so it could never match a real provider. This adds the
explicit path AIUI's model picker needs: /aiui/api/routstr/models
passes through the live aggregator catalog (the instance routstr.com's
own frontend queries; the canonical api.routstr.com 404s), and
/aiui/api/routstr/chat/completions makes one paid, non-streaming,
OpenAI-shaped call — session-gated, egress-screened (S3), refused
without an armed operator budget (D-05), paid via auto_pay_token,
change and refused-request tokens redeemed back into the wallet so a
failed attempt nets zero (verified live: quoted=1 reclaimed=1 net=0).
nginx template gains the location in both server blocks (T-13-15).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 13:41:07 -04:00
archipelagoandClaude Fable 5 ced95a60d1 feat(wallet): Lightning gets the arrival screen; copy buttons unified
Demo images / Build & push demo images (push) Successful in 3m27s
- lnd.createinvoice now returns r_hash_hex; new lnd.invoicestatus RPC
  looks the invoice up (SETTLED + amt_paid_sat). E2E-verified on this
  box: real invoice minted, status polls settled:false until paid.
- Receive modal: Lightning polls settlement every 3s and flips to the
  on-chain-style success view — straight to the green check + amount
  (no broadcast step; settlement is final). Raw bolt11 text removed:
  QR + CopyButton only. State fully reset per open/close.
- CopyButton is now the wallet's only copy affordance: the ark-address
  and ecash-token holdouts swapped in, their ad-hoc handlers deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 09:43:30 -04:00
archipelagoandClaude Fable 5 a0bd9e53f8 feat(settings): CA generation from the UI; Routstr panel beside the API key
Demo images / Build & push demo images (push) Successful in 3m36s
WebUI RULE (operator, 2026-08-14): never point users at a terminal. The
certificate section told users to run setup-node-ca.sh by hand — it now
has a Generate button backed by system.node-ca.generate, which runs the
idempotent script server-side (live-tested: generated and /ca.crt serves).
Routstr budget panel moves directly under the Claude API key card.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 09:19:05 -04:00
archipelagoandClaude Fable 5 6137762786 feat(settings): Routstr AI budget panel — the integration's missing switch
Demo images / Build & push demo images (push) Successful in 3m29s
The Routstr backend (Cashu-paid inference fallback, shipped 1.7.127) was
fully wired but permanently dormant: its D-05 gate requires an
operator-set sats allowance and nothing in the UI ever called
assistant.budget-get/set — default 0 meant never selected. New Settings
panel (below AI Data Access): allowance/spent/remaining, set-allowance
with 0-disables semantics, enabled/off badge. Backend untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 08:57:53 -04:00
archipelagoandClaude Fable 5 8d1fda29fa feat(federation): peer requests take the stage — clustered and faced
Demo images / Build & push demo images (push) Successful in 3m10s
Requests were spread evenly around the orbit, so they could sit BEHIND
the globe: the blinking call-to-action was invisible and the chart read
as mis-scaled until the user hand-rotated. Now requests cluster tightly
at one stage angle (spacing shrinks as count grows) and, when a NEW
request arrives, the camera steers to face the cluster front-and-center
(depth ∝ sin(angle−rotY); front = angle+π/2) — arrival only, so a user
who rotates away isn't fought. Static/reduced-motion paths snap+render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 07:36:38 -04:00
archipelago 135fb5650b chore: release v1.8.3-alpha
Demo images / Build & push demo images (push) Successful in 3m43s
2026-08-14 06:34:47 -04:00
archipelagoandClaude Fable 5 1de4a0943e docs(changelog): curate v1.8.3-alpha notes + What's New block
Demo images / Build & push demo images (push) Successful in 3m42s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 05:13:43 -04:00
archipelagoandClaude Fable 5 b5e33784e6 fix(ui): kiosk map animates again + paints on resize; gloss scoped; icons get intrinsic size
Demo images / Build & push demo images (push) Successful in 3m42s
- NetworkMap3D: kiosks keep static PLACEMENT (the rAF-fragile intro was
  the blank-screen cause) but re-attach the half-rate ticker — the calm
  orbit is back; and measure() now renders explicitly when no ticker runs,
  so resizes repaint instead of leaving a stale/blank/mis-scaled
  projection (also fixes reduced-motion users on any screen).
- Gloss v3 scoped to .logo-gloss opt-in (screensaver, intro, login,
  splash tap-logo) — it had leaked onto every logo-gradient-border user,
  including the dashboard header, via AnimatedLogo's default border.
- normalize-app-icon.py output now carries intrinsic 512x512 dimensions:
  a viewBox-only SVG collapses to nothing in auto-sized tiles (the
  'transparent icon in My Apps' report); both app icons regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 05:11:54 -04:00
archipelago 2b7e92e770 chore: release v1.8.2-alpha
Demo images / Build & push demo images (push) Successful in 3m43s
2026-08-14 04:28:12 -04:00
archipelagoandClaude Fable 5 7c34df36cd feat(ui): app icons on the house canvas; detail page gets the tile treatment
Demo images / Build & push demo images (push) Successful in 3m33s
- alby-hub + phoenixd icons re-set with the standard 12% inner margin
  (they shipped edge-to-edge; every other icon carries whitespace).
- scripts/normalize-app-icon.py: wraps any third-party SVG mark onto the
  house canvas — the system applies the tile plate (archy-app-icon)
  automatically but deliberately no runtime inset, so the margin must be
  baked; the guide now says exactly that.
- MarketplaceAppDetails: the icon now carries archy-app-icon like the
  store tiles — the treatment no longer stops at the detail page.
- v1.8.2 changelog: third curated bullet (the ceremony gate requires 3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 04:22:28 -04:00
archipelagoandClaude Fable 5 4b4e1ab1a3 feat(ui): Kammergut gloss v3 — wet black paint on the logo badge, approved
Demo images / Build & push demo images (push) Successful in 3m43s
Pure-CSS build (the plan-b SVG text filter embosses artifacts on a disc
— v1 rejected for exactly that): borderless painted disc with warm
Kammergut-toned light, dense gradient stops + turbulence grain dither
(banding), and a radial top bloom instead of a linear streak (a linear
streak's tips seamed against the rim — operator screenshot). Iterated
headlessly + on a live preview server; operator approved 2026-08-14.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 04:11:42 -04:00
archipelagoandClaude Fable 5 d05fa6988d fix(federation): kiosk map goes fully static — kills the blank-load stall
Demo images / Build & push demo images (push) Successful in 3m31s
The GSAP entrance intro needs healthy rAF delivery to reach opacity 1;
on a paint-starved kiosk it stalls and the federation/peers screen reads
as BLANK until a lucky refresh. Kiosks now take the existing staticMode
branch (no intro, no ticker — everything lands in place instantly); the
2D default and 2D/3D toggle stay. Half-rate tick kept for any future
non-static kiosk path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 03:57:05 -04:00
archipelagoandClaude Fable 5 2399eeac66 feat(ui): auto-tab fallback — embed-refusing apps become tab apps
Demo images / Build & push demo images (push) Successful in 3m49s
An app whose frame never loads while its backend reports Running (the
embed-refusal signature: frame-busting JS, top-level-origin apps,
SameSite=Strict logins — everything the gate's header stripping cannot
fix) is remembered in localStorage; every later launch opens a tab
straight from the click (user gesture, so no popup blocker), and
opensInTab() gives it the tab-launch icon. A successful iframe load
clears the memory and entries expire after 7 days, so nodes that gain
embedding (gate improvements) get re-probed instead of being remembered
broken forever. Dev guide updated; v1.8.2 changelog + What's New curated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 20:23:04 -04:00
archipelago 63cb9dd22c Revert "feat(ui): Kammergut gloss test on the logo badge inner circle"
Demo images / Build & push demo images (push) Successful in 3m51s
This reverts commit 6672d978f7.
2026-08-13 15:02:36 -04:00
123 changed files with 1796 additions and 283 deletions
+12
View File
@@ -1,5 +1,17 @@
# Changelog # Changelog
## v1.8.3-alpha (2026-08-14)
- **The network map on TVs: no more blank page, no more frozen page — and it moves again.** The map's entrance animation needed a smoothness that TV kiosk hardware can't always deliver, so the page could sit blank until a refresh; the previous fix cured the freeze by stopping the animation entirely, which went too far. Now the map appears instantly with everything already in place, then resumes its calm orbital motion at a gentler pace suited to TVs. Resizing or rotating any screen also redraws the map properly instead of leaving it tiny, stretched, or empty.
- **The dashboard's corner logo is back to normal.** The new glossy paint finish was meant for the big emblem on the screensaver, intro, and login screens — it had quietly spread to the small logo in the dashboard header, where it looked wrong. Each screen now gets exactly the treatment intended for it.
- **App icons no longer vanish in My Apps.** The freshly restyled Alby Hub and phoenixd icons could render as blank squares in some views — a subtlety in how the icon files declared their size. Fixed at the source, and the icon tool app developers use now produces immune files.
## v1.8.2-alpha (2026-08-13)
- **An app that can't be shown inside the dashboard now becomes a tab app by itself.** A few apps refuse to render inside another page no matter what — they break out with their own code or insist on owning the whole browser window. Opening one used to mean staring at a grey pane. Now the dashboard notices, offers the app in its own tab, and remembers: from then on that app's button opens a tab directly (with the little launch icon that tab apps carry), first click, every time. If a later update makes the app embeddable after all, the dashboard notices that too and goes back to embedding it.
- **The logo emblem got its glossy black paint finish — properly this time.** The circle behind the A on the screensaver, intro, and login now wears a deep wet-paint look: warm light blooming from the top edge, fine grain so the dark tones stay smooth instead of banding, and no more ring border. (An earlier rougher version of this experiment briefly shipped by accident and then vanished depending on which screen you were on — this is the finished, deliberate one, everywhere.)
- **New app icons now match the store's look, on every screen.** Alby Hub and phoenixd arrived with edge-to-edge logos that ignored the breathing room every other app icon has, and the app detail page skipped the icon backdrop entirely. Both icons are re-set on the standard canvas, the detail page now applies the same icon treatment as the store tiles, and app developers get a one-command tool that puts any logo onto the house canvas automatically.
## v1.8.1-alpha (2026-08-13) ## v1.8.1-alpha (2026-08-13)
- **Apps that refused to open inside the dashboard now embed like everything else.** Some apps ship browser headers that forbid being shown inside another page — correct hardening on the open web, but inside Archipelago it produced a dead grey pane when you opened them from My Apps (Alby Hub was the first to hit it). The app gate, which already checks your login on every request to an app, now removes just those framing headers on the way through; each app's own content-security rules pass through untouched. No more per-app proxy workarounds. - **Apps that refused to open inside the dashboard now embed like everything else.** Some apps ship browser headers that forbid being shown inside another page — correct hardening on the open web, but inside Archipelago it produced a dead grey pane when you opened them from My Apps (Alby Hub was the first to hit it). The app gate, which already checks your login on every request to an app, now removes just those framing headers on the way through; each app's own content-security rules pass through untouched. No more per-app proxy workarounds.
@@ -93,10 +93,11 @@ describe('useAI', () => {
expect(activeModel.value).toBe('echo') expect(activeModel.value).toBe('echo')
}) })
it('lists available providers with models', () => { it('lists available providers with models, Routstr first', () => {
const { availableProviders } = useAI() const { availableProviders } = useAI()
expect(availableProviders.value.length).toBe(3) expect(availableProviders.value.length).toBe(4)
const ids = availableProviders.value.map(p => p.id) const ids = availableProviders.value.map(p => p.id)
expect(ids[0]).toBe('routstr')
expect(ids).toContain('claude') expect(ids).toContain('claude')
expect(ids).toContain('openrouter') expect(ids).toContain('openrouter')
expect(ids).toContain('mock') expect(ids).toContain('mock')
@@ -119,7 +119,7 @@
<Transition name="picker"> <Transition name="picker">
<div <div
v-if="showModelPicker" v-if="showModelPicker"
class="fixed z-[9999] path-glass-card header-overlay-panel p-3 space-y-3 animate-fade-up-fast shadow-2xl min-w-[220px]" class="fixed z-[9999] path-glass-card header-overlay-panel p-3 space-y-3 animate-fade-up-fast shadow-2xl min-w-[220px] max-h-[70vh] overflow-y-auto"
:style="modelPickerDropdownStyle" :style="modelPickerDropdownStyle"
@click.stop @click.stop
> >
@@ -332,7 +332,7 @@ const modelDisplayName = computed(() => {
}) })
function selectModel(providerId: string, modelId: string) { function selectModel(providerId: string, modelId: string) {
setProvider(providerId as 'claude' | 'openrouter' | 'mock') setProvider(providerId as 'routstr' | 'claude' | 'openrouter' | 'mock')
setModel(modelId) setModel(modelId)
showModelPicker.value = false showModelPicker.value = false
} }
+118 -5
View File
@@ -13,12 +13,14 @@ import { useCodeContext } from '@/composables/useCodeContext'
import { apiFetch } from '@/utils/api-fetch' import { apiFetch } from '@/utils/api-fetch'
import { useSettingsStore } from '@/stores/settings' import { useSettingsStore } from '@/stores/settings'
type Provider = 'claude' | 'openrouter' | 'mock' type Provider = 'routstr' | 'claude' | 'openrouter' | 'mock'
// API paths are relative to the base URL so they work both in dev (/) and Archy (/aiui/) // API paths are relative to the base URL so they work both in dev (/) and Archy (/aiui/)
const BASE = import.meta.env.BASE_URL || '/' const BASE = import.meta.env.BASE_URL || '/'
const CLAUDE_PATH = `${BASE}api/claude/v1/messages` const CLAUDE_PATH = `${BASE}api/claude/v1/messages`
const OPENROUTER_PATH = `${BASE}api/openrouter` const OPENROUTER_PATH = `${BASE}api/openrouter`
const ROUTSTR_MODELS_PATH = `${BASE}api/routstr/models`
const ROUTSTR_CHAT_PATH = `${BASE}api/routstr/chat/completions`
import { mockFilms } from '@/mocks/films' import { mockFilms } from '@/mocks/films'
import { mockSongs } from '@/mocks/songs' import { mockSongs } from '@/mocks/songs'
@@ -148,8 +150,41 @@ function looksLikeMissingApiKey(err: string): boolean {
) )
} }
// ─── Routstr model catalog (fetched from the node's session-gated proxy) ───
// The node forwards the live Routstr aggregator's /v1/models; entries carry
// sats_pricing so completions are Cashu-paid against the operator's budget.
const routstrModels = ref<{ id: string; name: string }[]>([])
let routstrModelsFetched = false
async function refreshRoutstrModels() {
if (routstrModelsFetched) return
routstrModelsFetched = true
try {
const res = await apiFetch(ROUTSTR_MODELS_PATH)
if (!res.ok) return
const data = await res.json()
if (Array.isArray(data?.data)) {
routstrModels.value = data.data
.filter((m: Record<string, unknown>) => typeof m.id === 'string')
.map((m: Record<string, unknown>) => ({
id: m.id as string,
name: (m.name as string) || (m.id as string),
}))
}
} catch {
routstrModelsFetched = false // allow a retry on the next send/open
}
}
const availableProviders = computed(() => { const availableProviders = computed(() => {
const providers: { id: Provider; name: string; models: { id: string; name: string }[] }[] = [ const providers: { id: Provider; name: string; models: { id: string; name: string }[] }[] = [
{
id: 'routstr',
name: 'Routstr (sats)',
models: routstrModels.value.length > 0
? routstrModels.value
: [{ id: 'routstr-unavailable', name: 'No models — node offline?' }],
},
{ {
id: 'claude', id: 'claude',
name: 'Claude (Max)', name: 'Claude (Max)',
@@ -381,6 +416,71 @@ async function streamOpenRouter(
}, onError, signal) }, onError, signal)
} }
/**
* Routstr: one paid, NON-streaming, OpenAI-shaped completion through the
* node's session-gated `/aiui/api/routstr/` forwarder. The node quotes a
* price from the live catalog, pays with a Cashu token against the
* operator's budget (Settings → System → Routstr AI budget), redeems the
* change, and passes the provider's JSON back. The full answer is emitted
* as a single token — streaming across a paid hop is the planned follow-up.
*/
async function streamRoutstr(
messages: ChatMessage[],
onToken: (text: string) => void,
onError: (err: string) => void,
systemPrompt: string,
signal?: AbortSignal,
): Promise<void> {
const wireMessages = [
{ role: 'system' as const, content: systemPrompt },
...messages.map((m) => ({ role: m.role as 'user' | 'assistant', content: m.content })),
]
const res = await apiFetch(ROUTSTR_CHAT_PATH, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: activeModel.value,
messages: wireMessages,
stream: false,
}),
signal,
})
const bodyText = await res.text().catch(() => '')
if (!res.ok) {
// The node's refusals carry a plain-language error.message (budget not
// set, budget spent, wallet can't fund) — surface it verbatim.
let msg = `Routstr error ${res.status}`
try {
const parsed = JSON.parse(bodyText)
// Node refusals use {error:{message}}; the upstream provider nests
// its own as {detail:{error:{message}}} or a plain {detail:"..."}.
const detail = parsed?.detail
msg =
parsed?.error?.message ??
detail?.error?.message ??
(typeof detail === 'string' ? detail : undefined) ??
msg
} catch { /* keep the status-only message */ }
onError(msg)
return
}
if (signal?.aborted) return
try {
const parsed = JSON.parse(bodyText)
const text = parsed?.choices?.[0]?.message?.content
if (typeof text === 'string' && text.length > 0) {
onToken(text)
} else {
onError('Routstr returned an empty response')
}
} catch {
onError('Routstr returned a malformed response')
}
}
/** /**
* Embedded-mode chat delegation (D-01/D-17): when AIUI is running inside * Embedded-mode chat delegation (D-01/D-17): when AIUI is running inside
* Archy, the model call, the tool-calling loop, and the model key all live * Archy, the model call, the tool-calling loop, and the model key all live
@@ -619,7 +719,11 @@ export async function streamWithModel(
activeModel.value = model activeModel.value = model
try { try {
if (useArchy().isEmbedded.value) { if (provider === 'routstr') {
// Explicitly chosen Routstr wins even embedded in Archy — the whole
// point of the picker entry is that it is a selection, not a fallback.
await streamRoutstr(history, onToken, onError, 'You are a helpful assistant.', signal)
} else if (useArchy().isEmbedded.value) {
// D-17: embedded mode delegates the loop, the tools and the key to // D-17: embedded mode delegates the loop, the tools and the key to
// Archy — provider/model selection here doesn't apply node-side. // Archy — provider/model selection here doesn't apply node-side.
await streamViaArchy(history, onToken, onError, signal) await streamViaArchy(history, onToken, onError, signal)
@@ -638,8 +742,9 @@ export async function streamWithModel(
export function useAI() { export function useAI() {
const chatStore = useChatStore() const chatStore = useChatStore()
// Fetch Wavlake catalog on first use (non-blocking) // Fetch Wavlake + Routstr catalogs on first use (non-blocking)
refreshWavlakeCatalog() refreshWavlakeCatalog()
refreshRoutstrModels()
function stopGeneration() { function stopGeneration() {
if (currentAbort) { if (currentAbort) {
@@ -712,7 +817,11 @@ export function useAI() {
const genParams = getConversationParams(chatStore) const genParams = getConversationParams(chatStore)
try { try {
if (useArchy().isEmbedded.value) { if (provider === 'routstr') {
// Explicitly chosen Routstr wins even embedded in Archy — a
// selection, not a fallback.
await streamRoutstr(history, onToken, onError, systemPrompt, signal)
} else if (useArchy().isEmbedded.value) {
// D-17: embedded mode delegates the loop, the tools and the key to // D-17: embedded mode delegates the loop, the tools and the key to
// Archy — provider/model selection here doesn't apply node-side. // Archy — provider/model selection here doesn't apply node-side.
await streamViaArchy(history, onToken, onError, signal) await streamViaArchy(history, onToken, onError, signal)
@@ -828,7 +937,11 @@ export function useAI() {
const genParams = getConversationParams(chatStore) const genParams = getConversationParams(chatStore)
try { try {
if (useArchy().isEmbedded.value) { if (provider === 'routstr') {
// Explicitly chosen Routstr wins even embedded in Archy — a
// selection, not a fallback.
await streamRoutstr(history, onToken, onError, systemPrompt, signal)
} else if (useArchy().isEmbedded.value) {
// D-17: embedded mode delegates the loop, the tools and the key to // D-17: embedded mode delegates the loop, the tools and the key to
// Archy — provider/model selection here doesn't apply node-side. // Archy — provider/model selection here doesn't apply node-side.
await streamViaArchy(history, onToken, onError, signal) await streamViaArchy(history, onToken, onError, signal)
+1 -1
View File
@@ -104,7 +104,7 @@ dependencies = [
[[package]] [[package]]
name = "archipelago" name = "archipelago"
version = "1.8.1-alpha" version = "1.8.3-alpha"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"archipelago-container", "archipelago-container",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "archipelago" name = "archipelago"
version = "1.8.1-alpha" version = "1.8.3-alpha"
edition = "2021" edition = "2021"
license.workspace = true license.workspace = true
description = "Archipelago Bitcoin Node OS - Native backend" description = "Archipelago Bitcoin Node OS - Native backend"
+9
View File
@@ -6,6 +6,7 @@ mod node_message;
mod proxy; mod proxy;
mod remote_input; mod remote_input;
mod remote_relay; mod remote_relay;
mod routstr_proxy;
mod websocket; mod websocket;
use crate::api::rpc::RpcHandler; use crate::api::rpc::RpcHandler;
@@ -449,6 +450,14 @@ impl ApiHandler {
self.handle_model_proxy(req_with_bytes, p).await self.handle_model_proxy(req_with_bytes, p).await
} }
// AIUI Routstr proxy — the explicit, user-selected Routstr
// provider (model catalog + Cashu-paid completions), same
// session-gate discipline as the model proxy above. D-05: paid
// requests are refused unless the operator has armed a budget.
(_, p) if p.starts_with("/aiui/api/routstr/") => {
self.handle_routstr_proxy(req_with_bytes, p).await
}
// Health — unauthenticated, returns JSON with service status // Health — unauthenticated, returns JSON with service status
(Method::GET, "/health") => { (Method::GET, "/health") => {
let recovery_complete = crate::crash_recovery::is_recovery_complete(); let recovery_complete = crate::crash_recovery::is_recovery_complete();
@@ -84,14 +84,14 @@ async fn route_model_proxy(
/// call back into `ApiHandler::is_authenticated` — keeping this small and /// call back into `ApiHandler::is_authenticated` — keeping this small and
/// dependency-free is what makes the 401 behaviour unit-testable without /// dependency-free is what makes the 401 behaviour unit-testable without
/// paying for a full `ApiHandler` in every test. /// paying for a full `ApiHandler` in every test.
async fn is_authenticated(session_store: &SessionStore, headers: &HeaderMap) -> bool { pub(super) async fn is_authenticated(session_store: &SessionStore, headers: &HeaderMap) -> bool {
match session::extract_session_cookie(headers) { match session::extract_session_cookie(headers) {
Some(token) => session_store.validate(&token).await, Some(token) => session_store.validate(&token).await,
None => false, None => false,
} }
} }
fn unauthorized() -> Response<Body> { pub(super) fn unauthorized() -> Response<Body> {
let body = serde_json::json!({ "error": "Unauthorized" }); let body = serde_json::json!({ "error": "Unauthorized" });
Response::builder() Response::builder()
.status(StatusCode::UNAUTHORIZED) .status(StatusCode::UNAUTHORIZED)
@@ -119,7 +119,7 @@ fn key_not_configured() -> Response<Body> {
/// backends' egress screen never sees the forwarder path — the standalone /// backends' egress screen never sees the forwarder path — the standalone
/// frontend posts FULL history and images straight here — so the forwarder /// frontend posts FULL history and images straight here — so the forwarder
/// screens for itself. 400, plain-language, never naming what matched. /// screens for itself. 400, plain-language, never naming what matched.
fn blocked_secret_shaped() -> Response<Body> { pub(super) fn blocked_secret_shaped() -> Response<Body> {
let body = serde_json::json!({ let body = serde_json::json!({
"error": "Blocked: this request contained secret-shaped content (e.g. a seed phrase, key, or token). It was not sent anywhere." "error": "Blocked: this request contained secret-shaped content (e.g. a seed phrase, key, or token). It was not sent anywhere."
}); });
@@ -134,12 +134,12 @@ fn blocked_secret_shaped() -> Response<Body> {
/// the assistant's secret-shape rules (G-B1) with this node's own secrets /// the assistant's secret-shape rules (G-B1) with this node's own secrets
/// as the deny corpus. Returns Some(kind) — kind only, never the value — /// as the deny corpus. Returns Some(kind) — kind only, never the value —
/// when the content must not leave. /// when the content must not leave.
async fn forward_screen(text: &str, data_dir: &Path) -> Option<&'static str> { pub(super) async fn forward_screen(text: &str, data_dir: &Path) -> Option<&'static str> {
let secrets = crate::assistant::egress::load_known_secrets(&data_dir.join("secrets")).await; let secrets = crate::assistant::egress::load_known_secrets(&data_dir.join("secrets")).await;
crate::assistant::egress::scan_secret_shapes(text, &secrets) crate::assistant::egress::scan_secret_shapes(text, &secrets)
} }
fn bad_gateway(msg: &str) -> Response<Body> { pub(super) fn bad_gateway(msg: &str) -> Response<Body> {
let body = serde_json::json!({ "error": msg }); let body = serde_json::json!({ "error": msg });
Response::builder() Response::builder()
.status(StatusCode::BAD_GATEWAY) .status(StatusCode::BAD_GATEWAY)
@@ -331,7 +331,7 @@ async fn forward(
/// same shape as `proxy.rs`'s peer-content Range streamer — so a /// same shape as `proxy.rs`'s peer-content Range streamer — so a
/// token-by-token reply doesn't wait for the full response before the first /// token-by-token reply doesn't wait for the full response before the first
/// byte reaches the browser. /// byte reaches the browser.
fn stream_response(resp: reqwest::Response) -> Result<Response<Body>> { pub(super) fn stream_response(resp: reqwest::Response) -> Result<Response<Body>> {
let status = resp.status().as_u16(); let status = resp.status().as_u16();
let headers = resp.headers().clone(); let headers = resp.headers().clone();
let mut builder = Response::builder().status(status); let mut builder = Response::builder().status(status);
@@ -0,0 +1,535 @@
//! Session-gated forwarder for `/aiui/api/routstr/*` — the explicit,
//! user-selected Routstr path (as opposed to `assistant/backends/routstr.rs`,
//! which is the D-04 fallback leg the operator never chooses directly).
//!
//! AIUI's model picker lists Routstr as a first-class provider; selecting one
//! of its models routes chat completions through here. Same discipline as
//! `model_proxy.rs`: auth is re-derived from the request's own session cookie
//! (never trusted to nginx), inbound `authorization`/`cookie` headers are
//! never forwarded, and every outbound body is egress-screened (S3) before it
//! leaves the node.
//!
//! Payment is Cashu, D-05-gated end to end: a request is refused unless the
//! operator has set a non-zero Routstr allowance (Settings → System), the
//! quoted price fits the remaining allowance, and `auto_pay_token` (the ONE
//! budget-capped payment primitive, T-13-89) agrees to build the token. The
//! provider's change (`X-Cashu` / `X-Cashu-Refund` response headers, per
//! docs.routstr.com) is redeemed back into the node wallet and only the net
//! is recorded against the allowance.
//!
//! Upstream is the public Routstr aggregator instance routstr.com itself
//! ships against (verified live 2026-08-14: `/v1/models` serves the full
//! catalog with `sats_pricing`; the canonical `api.routstr.com` host 404s).
//! Making the instance operator-configurable — or sourcing it from the Nostr
//! provider announcements once those carry real endpoint/pricing content —
//! is the planned follow-up, not this file's job.
use super::ApiHandler;
use crate::session::SessionStore;
use anyhow::Result;
use hyper::{Body, Method, Request, Response, StatusCode};
use serde_json::{json, Value};
use std::path::Path;
use std::sync::Mutex as StdMutex;
use std::sync::OnceLock;
use std::time::{Duration, Instant};
use super::model_proxy::{
bad_gateway, blocked_secret_shaped, forward_screen, is_authenticated, unauthorized,
};
/// The live public Routstr aggregator (the same instance routstr.com's own
/// frontend queries for `/v1/providers` and `/v1/models`).
const ROUTSTR_INSTANCE: &str = "https://routstr.otrta.me";
/// Generation cap forced onto every forwarded completion — never unbounded
/// (T-13-88), and the completion half of the price quote is arithmetic over
/// exactly this figure.
const MAX_COMPLETION_TOKENS: u64 = 1024;
/// Same round-trip ceiling as `model_proxy.rs`'s Claude/Ollama forwarders.
const FORWARD_TIMEOUT_SECS: u64 = 180;
/// Models-catalog cache TTL — mirrors `backends/routstr.rs`'s provider
/// discovery TTL. The catalog prices every chat request, so it cannot be
/// fetched per-message without doubling latency.
const MODELS_CACHE_TTL: Duration = Duration::from_secs(300);
/// One model's sats-denominated pricing, parsed from the aggregator's
/// `/v1/models` entries (`sats_pricing`). Rates are sats PER TOKEN (fractional
/// floats); `request` is a flat per-request fee in sats. Untrusted input — a
/// missing/garbled field parses as 0.0 and simply prices low, which the
/// remaining-allowance ceiling still caps.
#[derive(Debug, Clone, Default, serde::Deserialize)]
struct SatsPricing {
#[serde(default)]
prompt: f64,
#[serde(default)]
completion: f64,
#[serde(default)]
request: f64,
}
/// Quote a price in whole sats for one completion call: flat request fee +
/// prompt rate × (payload chars / 4, the usual chars-per-token rule of thumb)
/// + completion rate × the forced `MAX_COMPLETION_TOKENS` cap, +20% margin,
/// rounded up, never below 1. Deliberately a pure function so the arithmetic
/// is unit-testable; deliberately conservative because the provider's change
/// comes back as a Cashu refund and is redeemed — overquoting costs nothing
/// but float, underquoting gets the request rejected upstream.
fn estimate_price_sats(pricing: &SatsPricing, prompt_chars: usize, completion_tokens: u64) -> u64 {
let prompt_tokens = (prompt_chars as f64) / 4.0;
let raw = pricing.request
+ pricing.prompt * prompt_tokens
+ pricing.completion * (completion_tokens as f64);
let with_margin = raw * 1.2;
(with_margin.ceil() as u64).max(1)
}
/// Process-lifetime cache of the aggregator's models catalog (same pattern as
/// `backends/routstr.rs`'s `PROVIDER_CACHE`).
static MODELS_CACHE: OnceLock<StdMutex<Option<(Instant, Value)>>> = OnceLock::new();
fn cached_models() -> Option<Value> {
let cache = MODELS_CACHE.get_or_init(|| StdMutex::new(None));
let guard = cache.lock().expect("routstr models cache poisoned");
guard.as_ref().and_then(|(at, models)| {
if at.elapsed() < MODELS_CACHE_TTL {
Some(models.clone())
} else {
None
}
})
}
fn set_cached_models(models: Value) {
let cache = MODELS_CACHE.get_or_init(|| StdMutex::new(None));
*cache.lock().expect("routstr models cache poisoned") = Some((Instant::now(), models));
}
/// Fetch (or serve cached) the aggregator's `/v1/models` catalog.
async fn fetch_models() -> Result<Value> {
if let Some(cached) = cached_models() {
return Ok(cached);
}
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(20))
.build()?;
let url = format!("{ROUTSTR_INSTANCE}/v1/models");
let resp = client.get(&url).send().await?;
if !resp.status().is_success() {
anyhow::bail!("routstr models upstream returned HTTP {}", resp.status());
}
let models: Value = resp.json().await?;
set_cached_models(models.clone());
Ok(models)
}
/// Find one model's `sats_pricing` in the catalog by exact id.
fn pricing_for_model(models: &Value, model_id: &str) -> Option<SatsPricing> {
models
.get("data")?
.as_array()?
.iter()
.find(|m| m.get("id").and_then(|v| v.as_str()) == Some(model_id))
.and_then(|m| m.get("sats_pricing"))
.and_then(|sp| serde_json::from_value(sp.clone()).ok())
}
fn json_response(status: StatusCode, body: Value) -> Response<Body> {
Response::builder()
.status(status)
.header("Content-Type", "application/json")
.body(Body::from(serde_json::to_vec(&body).unwrap_or_default()))
.unwrap_or_else(|_| Response::new(Body::from("{}")))
}
/// Plain-language refusal naming the UI path that fixes it — never a bare
/// status (RULE: every action needs a UI path).
fn budget_refusal(msg: String) -> Response<Body> {
json_response(
StatusCode::SERVICE_UNAVAILABLE,
json!({ "error": { "message": msg } }),
)
}
impl ApiHandler {
/// Entry point wired into the `/aiui/api/routstr/` arm in `mod.rs` —
/// thin, like `handle_model_proxy`, so the routing/budget logic below is
/// testable without a full `ApiHandler`.
pub(super) async fn handle_routstr_proxy(
&self,
req: Request<Body>,
path: &str,
) -> Result<Response<Body>> {
route_routstr_proxy(&self.session_store, &self.config.data_dir, req, path).await
}
}
async fn route_routstr_proxy(
session_store: &SessionStore,
data_dir: &Path,
req: Request<Body>,
path: &str,
) -> Result<Response<Body>> {
if !is_authenticated(session_store, req.headers()).await {
tracing::warn!("401 routstr proxy {} — session invalid or missing", path);
return Ok(unauthorized());
}
match path.strip_prefix("/aiui/api/routstr/") {
Some("models") if req.method() == Method::GET => forward_models().await,
Some("chat/completions") if req.method() == Method::POST => {
forward_chat(req, data_dir).await
}
_ => Ok(unauthorized()),
}
}
/// GET /aiui/api/routstr/models — the full catalog, passed through so AIUI
/// can render ids/names and show sats pricing. Read-only and unpaid.
async fn forward_models() -> Result<Response<Body>> {
match fetch_models().await {
Ok(models) => Ok(json_response(StatusCode::OK, models)),
Err(e) => {
tracing::warn!("routstr proxy: models upstream failed: {}", e);
Ok(bad_gateway("Routstr model catalog is unreachable"))
}
}
}
/// POST /aiui/api/routstr/chat/completions — one paid, non-streaming,
/// OpenAI-shaped completion. Order matters: screen (S3) → budget gate (D-05,
/// offline) → price quote → pay → forward → redeem change → record net.
async fn forward_chat(req: Request<Body>, data_dir: &Path) -> Result<Response<Body>> {
let payload = hyper::body::to_bytes(req.into_body())
.await
.map_err(|e| anyhow::anyhow!("read request payload: {e}"))?;
let payload_str = String::from_utf8_lossy(&payload).to_string();
// S3: the standalone frontend posts full history straight here with no
// assistant loop (and no egress screen) behind it.
if let Some(kind) = forward_screen(&payload_str, data_dir).await {
tracing::error!(
kind,
"routstr proxy: blocked chat forward — secret-shaped content"
);
return Ok(blocked_secret_shaped());
}
let mut body: Value = match serde_json::from_str(&payload_str) {
Ok(v) => v,
Err(_) => {
return Ok(json_response(
StatusCode::BAD_REQUEST,
json!({ "error": { "message": "request body is not valid JSON" } }),
));
}
};
let Some(model_id) = body.get("model").and_then(|v| v.as_str()).map(String::from) else {
return Ok(json_response(
StatusCode::BAD_REQUEST,
json!({ "error": { "message": "request is missing a model id" } }),
));
};
// D-05 gate, checked before any network I/O: a zero allowance means
// Routstr is refused outright, with the UI path that arms it.
let mut budget = crate::assistant::AssistantBudget::load(data_dir).await;
if budget.allowance_sats == 0 {
return Ok(budget_refusal(
"Routstr is disabled on this node — set a sats budget in Settings → System → \
Routstr AI budget to enable it."
.to_string(),
));
}
let remaining = budget.remaining_sats();
if remaining == 0 {
return Ok(budget_refusal(format!(
"This period's Routstr budget is spent ({} of {} sats). Raise the allowance in \
Settings → System → Routstr AI budget to continue.",
budget.spent_sats, budget.allowance_sats
)));
}
// Price the request from the catalog. An unknown model is a caller bug
// (the dropdown only offers catalog models), not a reason to guess a
// price.
let models = match fetch_models().await {
Ok(m) => m,
Err(e) => {
tracing::warn!("routstr proxy: cannot price request, models fetch failed: {e}");
return Ok(bad_gateway(
"Routstr model catalog is unreachable — cannot price this request",
));
}
};
let Some(pricing) = pricing_for_model(&models, &model_id) else {
return Ok(json_response(
StatusCode::BAD_REQUEST,
json!({ "error": { "message": format!("unknown Routstr model: {model_id}") } }),
));
};
// Force the shape this forwarder actually supports: non-streaming, with
// an explicit, capped generation limit (T-13-88).
let max_tokens = body
.get("max_tokens")
.and_then(|v| v.as_u64())
.unwrap_or(MAX_COMPLETION_TOKENS)
.min(MAX_COMPLETION_TOKENS);
body["stream"] = json!(false);
body["max_tokens"] = json!(max_tokens);
let price_sats = estimate_price_sats(&pricing, payload_str.len(), max_tokens);
if price_sats > remaining {
return Ok(budget_refusal(format!(
"This request quotes ~{price_sats} sats but only {remaining} sats remain in this \
period's Routstr budget (Settings → System → Routstr AI budget)."
)));
}
// Pay via the ONE budget-capped primitive (T-13-89) — same call, same
// mint list as the fallback leg in backends/routstr.rs.
let accepted_mints = crate::wallet::ecash::load_accepted_mints(data_dir)
.await
.map(|m| m.mints)
.unwrap_or_default();
let token = match crate::swarm::payment::auto_pay_token(
data_dir,
&budget.payment_policy(),
&accepted_mints,
price_sats,
)
.await?
{
Some(t) => t,
None => {
return Ok(budget_refusal(format!(
"The node wallet could not fund this request (~{price_sats} sats) — check the \
ecash balance and accepted mints in Settings → Wallet."
)));
}
};
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(FORWARD_TIMEOUT_SECS))
.build()?;
let url = format!("{ROUTSTR_INSTANCE}/v1/chat/completions");
let resp = match client
.post(&url)
.header("Authorization", format!("Bearer {token}"))
.header("Content-Type", "application/json")
.json(&body)
.send()
.await
{
Ok(r) => r,
Err(e) => {
// The token never reached the provider — reclaim it into our own
// wallet so the sats aren't stranded, and record nothing.
match crate::wallet::ecash::receive_token(data_dir, &token).await {
Ok(_) => tracing::info!(
"routstr proxy: upstream send failed ({e}); unsent payment token reclaimed"
),
Err(re) => tracing::warn!(
"routstr proxy: upstream send failed ({e}) AND reclaiming the unsent token \
failed ({re}) — {price_sats} sats may be stranded in the token"
),
}
return Ok(bad_gateway("Routstr provider is unreachable"));
}
};
let status = resp.status();
// Change comes back as a Cashu token header (docs.routstr.com names both
// spellings across versions); redeem it so only the net leaves the
// allowance.
let refund_token = ["x-cashu-refund", "x-cashu"]
.iter()
.find_map(|h| resp.headers().get(*h))
.and_then(|v| v.to_str().ok())
.map(String::from);
let resp_body = resp.bytes().await.unwrap_or_default();
let mut reclaimed = 0u64;
if let Some(refund) = refund_token {
match crate::wallet::ecash::receive_token(data_dir, &refund).await {
Ok(sats) => reclaimed = sats,
Err(e) => tracing::warn!("routstr proxy: redeeming the change token failed: {e}"),
}
} else if !status.is_success() {
// The provider refused the request (e.g. "mint unreachable") and
// sent no change — if it never actually redeemed our token, the
// proofs are still ours to take back. If it DID redeem and then
// failed, this reclaim fails harmlessly and the spend stands.
match crate::wallet::ecash::receive_token(data_dir, &token).await {
Ok(sats) => {
reclaimed = sats;
tracing::info!(
"routstr proxy: upstream refused (HTTP {status}); unredeemed payment token \
reclaimed ({sats} sats)"
);
}
Err(e) => tracing::warn!(
"routstr proxy: upstream refused (HTTP {status}) and the payment token could \
not be reclaimed ({e}) — treating the {price_sats} sats as spent"
),
}
}
let net_sats = price_sats.saturating_sub(reclaimed);
if net_sats > 0 {
if let Err(e) = budget.record_spend(data_dir, net_sats).await {
tracing::warn!(
error = %e,
"routstr proxy: failed to persist the budget spend (the payment itself already happened)"
);
}
}
tracing::info!(
model = %model_id,
quoted = price_sats,
reclaimed,
net = net_sats,
status = %status,
"routstr proxy: forwarded paid chat completion"
);
Ok(Response::builder()
.status(status.as_u16())
.header("Content-Type", "application/json")
.body(Body::from(resp_body))
.unwrap_or_else(|_| Response::new(Body::from("{}"))))
}
#[cfg(test)]
mod tests {
use super::*;
async fn test_store() -> SessionStore {
let path = std::env::temp_dir().join(format!(
"archy-routstr-proxy-test-sessions-{}.json",
rand::RngCore::next_u64(&mut rand::rngs::OsRng)
));
SessionStore::new_for_tests(path)
}
fn req(method: &str, path: &str, cookie: Option<&str>, body: &'static str) -> Request<Body> {
let mut builder = Request::builder().method(method).uri(path);
if let Some(c) = cookie {
builder = builder.header("cookie", format!("session={c}"));
}
builder.body(Body::from(body)).unwrap()
}
#[tokio::test]
async fn models_without_session_is_401() {
let store = test_store().await;
let data_dir = tempfile::tempdir().unwrap();
let r = req("GET", "/aiui/api/routstr/models", None, "");
let resp = route_routstr_proxy(&store, data_dir.path(), r, "/aiui/api/routstr/models")
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn chat_without_session_is_401() {
let store = test_store().await;
let data_dir = tempfile::tempdir().unwrap();
let r = req("POST", "/aiui/api/routstr/chat/completions", None, "{}");
let resp = route_routstr_proxy(
&store,
data_dir.path(),
r,
"/aiui/api/routstr/chat/completions",
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
/// D-05: a fresh node (no budget file → zero allowance) refuses the paid
/// path BEFORE any pricing/network I/O — this test runs fully offline.
#[tokio::test]
async fn chat_with_zero_allowance_is_refused_offline() {
let store = test_store().await;
let token = store.create().await;
let data_dir = tempfile::tempdir().unwrap();
let r = req(
"POST",
"/aiui/api/routstr/chat/completions",
Some(&token),
r#"{"model":"some-model","messages":[{"role":"user","content":"hi"}]}"#,
);
let resp = route_routstr_proxy(
&store,
data_dir.path(),
r,
"/aiui/api/routstr/chat/completions",
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::SERVICE_UNAVAILABLE);
let body = hyper::body::to_bytes(resp.into_body()).await.unwrap();
let v: Value = serde_json::from_slice(&body).unwrap();
let msg = v["error"]["message"].as_str().unwrap();
assert!(msg.contains("Settings"), "refusal must name the UI path");
}
#[tokio::test]
async fn chat_body_carrying_bip39_is_blocked() {
let store = test_store().await;
let token = store.create().await;
let data_dir = tempfile::tempdir().unwrap();
std::fs::create_dir_all(data_dir.path().join("secrets")).unwrap();
let r = req(
"POST",
"/aiui/api/routstr/chat/completions",
Some(&token),
r#"{"model":"m","messages":[{"role":"user","content":"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"}]}"#,
);
let resp = route_routstr_proxy(
&store,
data_dir.path(),
r,
"/aiui/api/routstr/chat/completions",
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn price_estimate_is_conservative_and_never_zero() {
// A free/garbled pricing entry still quotes at least 1 sat.
assert_eq!(estimate_price_sats(&SatsPricing::default(), 100, 1024), 1);
// Live-catalog-shaped numbers (deepseek-v4-flash, 2026-08-14):
// request 0.001, prompt ~0.000178/tok, completion ~0.000267/tok.
let p = SatsPricing {
prompt: 0.000178,
completion: 0.000267,
request: 0.001,
};
let quote = estimate_price_sats(&p, 4000, 1024);
// ~0.18 + ~0.27 + flat, with margin → rounds up to 1 sat.
assert_eq!(quote, 1);
// A pricier model scales with the prompt.
let expensive = SatsPricing {
prompt: 0.05,
completion: 0.1,
request: 1.0,
};
let quote = estimate_price_sats(&expensive, 40_000, 1024);
assert!(quote >= 600, "quote {quote} should reflect real rates");
}
#[test]
fn pricing_lookup_finds_exact_model_id() {
let models = json!({ "data": [
{ "id": "a-model", "sats_pricing": { "prompt": 0.1, "completion": 0.2, "request": 1.0 } },
{ "id": "other", "sats_pricing": { "prompt": 0.3 } }
]});
let p = pricing_for_model(&models, "a-model").unwrap();
assert_eq!(p.request, 1.0);
assert!(pricing_for_model(&models, "missing").is_none());
}
}
@@ -344,6 +344,7 @@ impl RpcHandler {
"content.indeehub-projects" => self.handle_content_indeehub_projects().await, "content.indeehub-projects" => self.handle_content_indeehub_projects().await,
"system.settings.get" => self.handle_system_settings_get(params).await, "system.settings.get" => self.handle_system_settings_get(params).await,
"system.settings.set" => self.handle_system_settings_set(params).await, "system.settings.set" => self.handle_system_settings_set(params).await,
"system.node-ca.generate" => self.handle_system_node_ca_generate().await,
"system.kiosk-display.get" => self.handle_system_kiosk_display_get().await, "system.kiosk-display.get" => self.handle_system_kiosk_display_get().await,
"system.kiosk-display.set" => self.handle_system_kiosk_display_set(params).await, "system.kiosk-display.set" => self.handle_system_kiosk_display_set(params).await,
"bitcoin.relay-update-settings" => { "bitcoin.relay-update-settings" => {
@@ -133,6 +133,7 @@ impl RpcHandler {
"lnd.sendcoins" => self.handle_lnd_sendcoins(params).await, "lnd.sendcoins" => self.handle_lnd_sendcoins(params).await,
"lnd.estimatefee" => self.handle_lnd_estimatefee(params).await, "lnd.estimatefee" => self.handle_lnd_estimatefee(params).await,
"lnd.createinvoice" => self.handle_lnd_createinvoice(params).await, "lnd.createinvoice" => self.handle_lnd_createinvoice(params).await,
"lnd.invoicestatus" => self.handle_lnd_invoicestatus(params).await,
"lnd.payinvoice" => self.handle_lnd_payinvoice(params).await, "lnd.payinvoice" => self.handle_lnd_payinvoice(params).await,
"lnd.paymentstatus" => self.handle_lnd_paymentstatus(params).await, "lnd.paymentstatus" => self.handle_lnd_paymentstatus(params).await,
"lnd.create-psbt" => self.handle_lnd_create_psbt(params).await, "lnd.create-psbt" => self.handle_lnd_create_psbt(params).await,
@@ -503,6 +504,7 @@ impl RpcHandler {
"ai.permissions.set" => self.handle_ai_permissions_set(params).await, "ai.permissions.set" => self.handle_ai_permissions_set(params).await,
"system.settings.get" => self.handle_system_settings_get(params).await, "system.settings.get" => self.handle_system_settings_get(params).await,
"system.settings.set" => self.handle_system_settings_set(params).await, "system.settings.set" => self.handle_system_settings_set(params).await,
"system.node-ca.generate" => self.handle_system_node_ca_generate().await,
"system.kiosk-display.get" => self.handle_system_kiosk_display_get().await, "system.kiosk-display.get" => self.handle_system_kiosk_display_get().await,
"system.kiosk-display.set" => self.handle_system_kiosk_display_set(params).await, "system.kiosk-display.set" => self.handle_system_kiosk_display_set(params).await,
@@ -607,9 +607,77 @@ impl RpcHandler {
.unwrap_or("") .unwrap_or("")
.to_string(); .to_string();
// LND returns r_hash base64-encoded; the lookup endpoint the Receive
// flow polls (`lnd.invoicestatus`) wants it hex — hand the UI the
// ready-to-use form.
let r_hash_hex = {
use base64::Engine as _;
body.get("r_hash")
.and_then(|v| v.as_str())
.and_then(|b64| base64::engine::general_purpose::STANDARD.decode(b64).ok())
.map(hex::encode)
.unwrap_or_default()
};
Ok(serde_json::json!({ Ok(serde_json::json!({
"payment_request": payment_request, "payment_request": payment_request,
"amount_sats": amount_sats, "amount_sats": amount_sats,
"r_hash_hex": r_hash_hex,
}))
}
/// lnd.invoicestatus — is this invoice settled yet? Polled by the wallet's
/// Receive flow so a Lightning payment gets the same "money has arrived"
/// success screen as on-chain (minus the broadcast step: settlement is
/// final). Params: `{ "r_hash_hex": string }`.
pub(in crate::api::rpc) async fn handle_lnd_invoicestatus(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
let r_hash_hex = params
.get("r_hash_hex")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing 'r_hash_hex' parameter"))?;
if r_hash_hex.len() != 64 || !r_hash_hex.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(anyhow::anyhow!("r_hash_hex must be 64 hex characters"));
}
let (client, macaroon_hex) = self.lnd_client().await?;
let resp = client
.get(format!("{LND_REST_BASE_URL}/v1/invoice/{r_hash_hex}"))
.header("Grpc-Metadata-macaroon", &macaroon_hex)
.send()
.await
.context("Failed to query invoice")?;
let status = resp.status();
let body: serde_json::Value = resp
.json()
.await
.context("Failed to parse invoice lookup response")?;
if !status.is_success() {
let msg = body
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("Unknown error");
return Err(anyhow::anyhow!("Invoice lookup failed: {}", msg));
}
let settled = body
.get("state")
.and_then(|v| v.as_str())
.map(|s| s == "SETTLED")
.unwrap_or_else(|| body.get("settled").and_then(|v| v.as_bool()).unwrap_or(false));
let amt_paid_sat = body
.get("amt_paid_sat")
.and_then(|v| v.as_str())
.and_then(|s| s.parse::<i64>().ok())
.or_else(|| body.get("amt_paid_sat").and_then(|v| v.as_i64()))
.unwrap_or(0);
Ok(serde_json::json!({
"settled": settled,
"amt_paid_sat": amt_paid_sat,
})) }))
} }
@@ -1248,6 +1248,30 @@ impl RpcHandler {
} }
} }
/// system.node-ca.generate — run the node's (idempotent) CA setup so the
/// dashboard can offer certificate generation as a button. WebUI rule:
/// users must never be pointed at a terminal; the script reuses an
/// existing CA and only reissues the leaf, so re-running is safe.
pub(in crate::api::rpc) async fn handle_system_node_ca_generate(
&self,
) -> Result<serde_json::Value> {
let script = "/opt/archipelago/scripts/setup-node-ca.sh";
if tokio::fs::metadata(script).await.is_err() {
anyhow::bail!(
"The certificate setup script is not on this node yet — it arrives with the next update."
);
}
let status = host_sudo(&["/usr/bin/bash", script]).await?;
if !status.success() {
anyhow::bail!(
"Certificate generation failed (exit {:?}) — see the node log for detail",
status.code()
);
}
info!("Node CA generated/reissued via dashboard");
Ok(serde_json::json!({ "generated": true }))
}
/// system.kiosk-display.get — Current kiosk display preset + whether this /// system.kiosk-display.get — Current kiosk display preset + whether this
/// node has a kiosk at all (no kiosk unit -> the Settings section hides). /// node has a kiosk at all (no kiosk unit -> the Settings section hides).
pub(in crate::api::rpc) async fn handle_system_kiosk_display_get( pub(in crate::api::rpc) async fn handle_system_kiosk_display_get(
+147 -21
View File
@@ -731,6 +731,71 @@ fn icon_markup(app: &GatedPort) -> String {
format!(r#"<div class="tile">{inner}</div>"#) format!(r#"<div class="tile">{inner}</div>"#)
} }
/// The dashboard login's badge, reproduced square-for-square: the same 20
/// white rects AnimatedLogo.vue draws, with the same 100ms stagger, inside
/// the same gradient ring. Inline rather than an `<img>` because the shipped
/// `favico-black-v2.svg` bakes its own ring into the artwork — wrapping it in
/// the CSS ring drew a ring inside a ring, which is not what /login shows.
/// (x, y, width, height) as they appear in AnimatedLogo.vue.
const LOGO_RECTS: [(f32, f32, f32, f32); 20] = [
(357.614, 318.0, 71.007, 70.936),
(436.152, 318.0, 72.082, 70.936),
(515.766, 318.0, 72.082, 70.936),
(595.379, 318.0, 71.007, 70.936),
(595.379, 396.46, 71.007, 72.011),
(673.917, 396.46, 72.083, 72.011),
(278.0, 475.994, 72.083, 72.012),
(357.614, 475.994, 71.007, 72.012),
(436.152, 475.994, 72.082, 72.012),
(515.766, 475.994, 72.082, 72.012),
(595.379, 475.994, 71.007, 72.012),
(673.917, 475.994, 72.083, 72.012),
(278.0, 555.529, 72.083, 70.936),
(357.614, 555.529, 71.007, 70.936),
(595.379, 555.529, 71.007, 70.936),
(673.917, 555.529, 72.083, 70.936),
(357.614, 633.989, 71.007, 72.011),
(436.152, 633.989, 72.082, 72.011),
(515.766, 633.989, 72.082, 72.011),
(595.379, 633.989, 71.007, 72.011),
];
fn logo_markup() -> String {
let rects: String = LOGO_RECTS
.iter()
.enumerate()
.map(|(i, (x, y, w, h))| {
format!(
r#"<rect x="{x}" y="{y}" width="{w}" height="{h}" fill="white" class="sq" style="--d:{delay}ms"/>"#,
delay = i * 100,
)
})
.collect();
format!(
r##"<div class="logo"><svg viewBox="0 0 1024 1024" role="img" aria-label="Archipelago" xmlns="http://www.w3.org/2000/svg"><rect width="1024" height="1024" fill="#030202"/>{rects}</svg></div>"##
)
}
/// The main login's in-button spinner, verbatim from Login.vue.
const SPINNER_SVG: &str = r#"<svg class="spin" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle style="opacity:.25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path style="opacity:.75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>"#;
/// Submit feedback: flip the pressed button into its loading face and stop a
/// second press, exactly as /login does. This is the only script on the page,
/// and the CSP admits it by hash — not `'unsafe-inline'` — so an injected
/// `<script>` still cannot run. Everything works without it (the form is a
/// plain POST); losing JS costs only the spinner.
const SUBMIT_FEEDBACK_JS: &str = "document.addEventListener('submit',function(e){var b=e.target.querySelector('button');if(b){b.classList.add('loading');b.disabled=true;}});";
/// `'sha256-…'` CSP source expression for [`SUBMIT_FEEDBACK_JS`]. Computed
/// from the constant itself so the two can never drift apart.
fn submit_feedback_csp_hash() -> String {
use sha2::{Digest, Sha256};
format!(
"'sha256-{}'",
base64_encode(&Sha256::digest(SUBMIT_FEEDBACK_JS.as_bytes()))
)
}
/// Icons live with the web UI. Only files under the icon directory are read, /// Icons live with the web UI. Only files under the icon directory are read,
/// and only known image extensions — the path comes from a manifest, which is /// and only known image extensions — the path comes from a manifest, which is
/// signed, but treating it as untrusted costs nothing. /// signed, but treating it as untrusted costs nothing.
@@ -782,7 +847,9 @@ const LOGIN_BACKGROUNDS: [&str; 4] = [
/// join: the name arrives in a URL, and the gate answers before any /// join: the name arrives in a URL, and the gate answers before any
/// authentication, so nothing here may be caller-controlled beyond this set. /// authentication, so nothing here may be caller-controlled beyond this set.
fn read_ui_asset(name: &str) -> Option<(Vec<u8>, &'static str)> { fn read_ui_asset(name: &str) -> Option<(Vec<u8>, &'static str)> {
let allowed = LOGIN_BACKGROUNDS.contains(&name) || name == "favico-black-v2.svg"; // Only the rotating backgrounds: the logo badge is inline SVG now, so no
// image asset backs it.
let allowed = LOGIN_BACKGROUNDS.contains(&name);
if !allowed { if !allowed {
return None; return None;
} }
@@ -904,8 +971,15 @@ main {{ position:relative; z-index:1; width:min(92vw,28rem); }}
width:5rem; height:5rem; border-radius:9999px; padding:3px; width:5rem; height:5rem; border-radius:9999px; padding:3px;
background:linear-gradient(135deg, rgba(255,255,255,.6) 0%, rgba(0,0,0,.8) 100%); background:linear-gradient(135deg, rgba(255,255,255,.6) 0%, rgba(0,0,0,.8) 100%);
box-shadow:0 8px 24px rgba(0,0,0,.5); }} box-shadow:0 8px 24px rgba(0,0,0,.5); }}
.logo img {{ width:100%; height:100%; border-radius:9999px; display:block; .logo::after {{ content:''; position:absolute; inset:3px; border-radius:9999px;
background:#000; padding:.5rem; }} background:#000; z-index:0; }}
.logo svg {{ position:relative; z-index:1; width:100%; height:100%;
border-radius:9999px; display:block; }}
/* AnimatedLogo.vue's reveal, timing intact: each square fades in on its own
100ms-step delay over a 3s loop. */
.logo .sq {{ opacity:0; animation:logo-square-in 3s ease-out infinite;
animation-delay:var(--d,0ms); animation-fill-mode:both; }}
@keyframes logo-square-in {{ 0% {{ opacity:0; }} 15% {{ opacity:1; }} 100% {{ opacity:1; }} }}
/* The app's own tile, in the My Apps shape: 18px-rounded square on dark /* The app's own tile, in the My Apps shape: 18px-rounded square on dark
glass with the same inner highlight and drop shadow. */ glass with the same inner highlight and drop shadow. */
.tile {{ width:60px; height:60px; border-radius:18px; margin:0 auto .75rem; .tile {{ width:60px; height:60px; border-radius:18px; margin:0 auto .75rem;
@@ -926,22 +1000,42 @@ input {{ width:100%; padding:.75rem 1rem; margin-bottom:1rem; border-radius:.5re
input::placeholder {{ color:rgba(255,255,255,.4); }} input::placeholder {{ color:rgba(255,255,255,.4); }}
input:focus {{ outline:none; border-color:rgba(255,255,255,.4); input:focus {{ outline:none; border-color:rgba(255,255,255,.4);
box-shadow:0 0 0 1px rgba(255,255,255,.2); }} box-shadow:0 0 0 1px rgba(255,255,255,.2); }}
button {{ width:100%; min-height:44px; padding:.75rem 1.25rem; border:none; /* .glass-button, longhand — the lift, the lightening and the rim glow on
border-radius:.75rem; background:rgba(0,0,0,.6); hover are what make the dashboard's buttons feel alive; the old flat
darken-only hover here read as broken next to /login. */
button {{ position:relative; display:inline-flex; align-items:center;
justify-content:center; width:100%; min-height:44px; padding:.75rem 1.25rem;
border:none; border-radius:.75rem; background:rgba(0,0,0,.6);
backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px); backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px);
box-shadow:0 8px 24px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.22); box-shadow:0 8px 24px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.22);
color:rgba(255,255,255,.9); font-size:1rem; font-weight:500; cursor:pointer; color:rgba(255,255,255,.9); font-size:1rem; font-weight:500; cursor:pointer;
transition:background-color .2s ease, transform .3s cubic-bezier(.4,0,.2,1); }} transition:transform .3s cubic-bezier(.4,0,.2,1), background-color .2s ease,
button:hover {{ background:rgba(0,0,0,.7); }} box-shadow .3s ease; }}
button::before {{ content:''; position:absolute; inset:0; border-radius:inherit;
padding:2px; background:linear-gradient(135deg, rgba(0,0,0,.8), transparent);
-webkit-mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite:xor; mask-composite:exclude; pointer-events:none; }}
button:hover {{ transform:translateY(-2px); background:rgba(0,0,0,.35);
box-shadow:0 12px 32px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.25); }}
button:hover::before {{ background:linear-gradient(135deg, rgba(255,255,255,.3), transparent); }}
button:active {{ transform:translateY(1px); }} button:active {{ transform:translateY(1px); }}
button:disabled {{ opacity:.5; cursor:not-allowed; transform:none; }}
/* Two faces per button; the submit-feedback script flips .loading on. */
button .busy {{ display:none; }}
button.loading .idle {{ display:none; }}
button.loading .busy {{ display:inline-flex; align-items:center; gap:.5rem; }}
.spin {{ width:1.25rem; height:1.25rem; animation:spin 1s linear infinite; }}
@keyframes spin {{ to {{ transform:rotate(360deg); }} }}
.err {{ background:rgba(239,68,68,.2); border:1px solid rgba(239,68,68,.4); .err {{ background:rgba(239,68,68,.2); border:1px solid rgba(239,68,68,.4);
color:#fecaca; padding:.75rem; border-radius:.5rem; margin-bottom:1rem; color:#fecaca; padding:.75rem; border-radius:.5rem; margin-bottom:1rem;
font-size:.875rem; text-align:left; }} font-size:.875rem; text-align:left; }}
</style></head> </style></head>
<body>{backgrounds}<main><div class="card">{body}</div></main></body></html>"#, <body>{backgrounds}<main><div class="card">{body}</div></main>
<script>{submit_feedback}</script></body></html>"#,
title = esc(title), title = esc(title),
app_name = esc(&app.app_name), app_name = esc(&app.app_name),
body = body, body = body,
submit_feedback = SUBMIT_FEEDBACK_JS,
backgrounds = background_layers(), backgrounds = background_layers(),
cycle = LOGIN_BACKGROUNDS.len() as u32 * 9, cycle = LOGIN_BACKGROUNDS.len() as u32 * 9,
hold = 100 / LOGIN_BACKGROUNDS.len() as u32, hold = 100 / LOGIN_BACKGROUNDS.len() as u32,
@@ -961,10 +1055,16 @@ button:active {{ transform:translateY(1px); }}
// login, on any port or scheme, which is exactly the dashboard. // login, on any port or scheme, which is exactly the dashboard.
// Anything else — another site embedding it to harvest the node // Anything else — another site embedding it to harvest the node
// password — is still refused. // password — is still refused.
// script-src admits exactly one script, by hash: the submit-feedback
// snippet above. Injected markup (an app name, an error string) still
// cannot execute — its hash would not match.
.header( .header(
"Content-Security-Policy", "Content-Security-Policy",
"default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; \ format!(
form-action 'self'; frame-ancestors 'self' http://*:* https://*:*", "default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; \
script-src {hash}; form-action 'self'; frame-ancestors 'self' http://*:* https://*:*",
hash = submit_feedback_csp_hash(),
),
) )
.body(Body::from(html)) .body(Body::from(html))
.expect("static response builds") .expect("static response builds")
@@ -975,15 +1075,17 @@ button:active {{ transform:translateY(1px); }}
/// password by an unexplained page. /// password by an unexplained page.
fn login_page(app: &GatedPort, error: Option<&str>, status: StatusCode) -> Response<Body> { fn login_page(app: &GatedPort, error: Option<&str>, status: StatusCode) -> Response<Body> {
let body = format!( let body = format!(
r#"<div class="logo"><img src="{prefix}asset/favico-black-v2.svg" alt="Archipelago"></div> r#"{logo}
{icon} {icon}
<h1>Sign in to open {name}</h1> <h1>Sign in to open {name}</h1>
<p class="sub">This app is protected by your node password.</p> <p class="sub">This app is protected by your node password.</p>
{err} {err}
<form method="post" action="{prefix}login"> <form method="post" action="{prefix}login">
<input type="password" name="password" placeholder="Node password" autocomplete="current-password" autofocus required> <input type="password" name="password" placeholder="Node password" autocomplete="current-password" autofocus required>
<button type="submit">Sign in</button> <button type="submit"><span class="idle">Sign in</span><span class="busy">{spinner}Signing in…</span></button>
</form>"#, </form>"#,
logo = logo_markup(),
spinner = SPINNER_SVG,
icon = icon_markup(app), icon = icon_markup(app),
name = esc(&app.app_name), name = esc(&app.app_name),
err = error err = error
@@ -1004,8 +1106,9 @@ fn totp_page(app: &GatedPort, error: Option<&str>, status: StatusCode) -> Respon
{err} {err}
<form method="post" action="{prefix}totp"> <form method="post" action="{prefix}totp">
<input type="text" name="code" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" placeholder="000000" autofocus required> <input type="text" name="code" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" placeholder="000000" autofocus required>
<button type="submit">Verify</button> <button type="submit"><span class="idle">Verify</span><span class="busy">{spinner}Verifying…</span></button>
</form>"#, </form>"#,
spinner = SPINNER_SVG,
icon = icon_markup(app), icon = icon_markup(app),
name = esc(&app.app_name), name = esc(&app.app_name),
err = error err = error
@@ -1200,25 +1303,48 @@ mod tests {
} }
/// The login page must render entirely from the gate's own origin: the /// The login page must render entirely from the gate's own origin: the
/// CSP allows no external host, so a background or logo that 404s leaves /// CSP allows no external host, so a background that 404s leaves a black
/// a black page rather than the dashboard's art. /// page rather than the dashboard's art. The badge itself is inline SVG —
/// the same 20 squares as the dashboard login's AnimatedLogo — so it can
/// never 404 at all.
#[tokio::test] #[tokio::test]
async fn login_page_sources_its_art_from_the_gate() { async fn login_page_sources_its_art_from_the_gate() {
let resp = login_page(&app(), None, StatusCode::UNAUTHORIZED); let resp = login_page(&app(), None, StatusCode::UNAUTHORIZED);
let body = hyper::body::to_bytes(resp.into_body()).await.unwrap(); let body = hyper::body::to_bytes(resp.into_body()).await.unwrap();
let html = String::from_utf8_lossy(&body).to_string(); let html = String::from_utf8_lossy(&body).to_string();
assert!(html.contains(&format!("{GATE_PREFIX}asset/favico-black-v2.svg"))); assert_eq!(
html.matches(r#"class="sq""#).count(),
LOGO_RECTS.len(),
"the badge must draw every AnimatedLogo square inline"
);
for name in LOGIN_BACKGROUNDS { for name in LOGIN_BACKGROUNDS {
assert!( assert!(
html.contains(&format!("{GATE_PREFIX}asset/{name}")), html.contains(&format!("{GATE_PREFIX}asset/{name}")),
"background {name} is not referenced" "background {name} is not referenced"
); );
} }
// Every referenced asset must be one the gate will actually serve. }
// The logo is the sidebar A mark (favico-black-v2.svg) since the
// 2026-08-05 login-page rework — the old wordmark is off the /// The only script the challenge pages may run is the submit-feedback
// allowlist on purpose. /// snippet, admitted by hash. The page must carry exactly that script,
assert!(read_ui_asset("favico-black-v2.svg").is_some() || cfg!(not(debug_assertions))); /// and the CSP must name its hash — anything injected has a different
/// hash and stays inert.
#[tokio::test]
async fn submit_feedback_script_is_present_and_hash_pinned() {
let resp = login_page(&app(), None, StatusCode::UNAUTHORIZED);
let csp = resp.headers()["Content-Security-Policy"]
.to_str()
.unwrap()
.to_string();
assert!(csp.contains(&format!("script-src {}", submit_feedback_csp_hash())));
assert!(!csp.contains("script-src 'unsafe-inline'"));
let body = hyper::body::to_bytes(resp.into_body()).await.unwrap();
let html = String::from_utf8_lossy(&body);
assert!(html.contains(&format!("<script>{SUBMIT_FEEDBACK_JS}</script>")));
// Both button faces render: idle label and the spinner face.
assert!(html.contains(r#"<span class="idle">Sign in</span>"#));
assert!(html.contains("Signing in…"));
assert!(html.contains(r#"class="spin""#));
} }
/// The allowlist is the whole security boundary for asset serving: the /// The allowlist is the whole security boundary for asset serving: the
+45
View File
@@ -171,6 +171,13 @@ pub async fn ensure_doctor_installed() {
Ok(false) => debug!("tor-helper.sh already current"), Ok(false) => debug!("tor-helper.sh already current"),
Err(e) => warn!("tor-helper sync failed (non-fatal): {:#}", e), Err(e) => warn!("tor-helper sync failed (non-fatal): {:#}", e),
} }
match run_welcome_banner_sync().await {
Ok(true) => info!(
"Console welcome banner synchronized (LAN address + .local name, not the WG tunnel IP)"
),
Ok(false) => debug!("Console welcome banner already current (or not an ISO node)"),
Err(e) => warn!("Welcome banner sync failed (non-fatal): {:#}", e),
}
match run_tor_torrc_repair().await { match run_tor_torrc_repair().await {
Ok(true) => info!("Tor healed at boot (torrc rebuilt and/or daemon restarted)"), Ok(true) => info!("Tor healed at boot (torrc rebuilt and/or daemon restarted)"),
Ok(false) => debug!("Tor healthy and torrc in sync — no heal needed"), Ok(false) => debug!("Tor healthy and torrc in sync — no heal needed"),
@@ -654,6 +661,44 @@ exit 2
const TOR_HELPER_SH: &str = include_str!("../../../scripts/tor-helper.sh"); const TOR_HELPER_SH: &str = include_str!("../../../scripts/tor-helper.sh");
const TOR_HELPER_PATH: &str = "/opt/archipelago/scripts/tor-helper.sh"; const TOR_HELPER_PATH: &str = "/opt/archipelago/scripts/tor-helper.sh";
/// The console welcome banner, embedded so the OTA can fix it on deployed
/// nodes. `/etc/profile.d/archipelago.sh` is baked by the ISO installer and
/// no OTA path touched it, so every node kept whatever its ISO generation
/// shipped — including banners that print the node's own WireGuard address
/// (10.44.0.1, present on EVERY node) as the "web ui", which is unreachable
/// off-tunnel and actively misleading after a move to a new network
/// (framework-pt, 2026-08-15). Canonical copy: scripts/welcome-banner.sh;
/// the ISO builder inlines the same content for fresh installs.
const WELCOME_BANNER_SH: &str = include_str!("../../../scripts/welcome-banner.sh");
const WELCOME_BANNER_PATH: &str = "/etc/profile.d/archipelago.sh";
async fn run_welcome_banner_sync() -> Result<bool> {
let current = tokio::fs::read_to_string(WELCOME_BANNER_PATH)
.await
.unwrap_or_default();
// Only refresh a banner the installer put there: a dev machine running
// the backend from a checkout has no business growing one in /etc.
if current.is_empty() || current == WELCOME_BANNER_SH {
return Ok(false);
}
let staged = "/var/lib/archipelago/welcome-banner.staged";
if let Some(dir) = Path::new(staged).parent() {
tokio::fs::create_dir_all(dir).await.ok();
}
tokio::fs::write(staged, WELCOME_BANNER_SH)
.await
.context("stage welcome banner")?;
let script = format!(
"set -eu\ninstall -m 0755 {staged} {dest}\nexit 0\n",
staged = staged,
dest = WELCOME_BANNER_PATH
);
host_sudo(&["sh", "-lc", &script])
.await
.context("install welcome banner")?;
Ok(true)
}
async fn run_tor_helper_sync() -> Result<bool> { async fn run_tor_helper_sync() -> Result<bool> {
let current = tokio::fs::read_to_string(TOR_HELPER_PATH) let current = tokio::fs::read_to_string(TOR_HELPER_PATH)
.await .await
+2 -2
View File
@@ -1,3 +1,3 @@
commit=7b82dfc779f94e068ed4d7b2ada39d6fd8f77dce commit=a4be1b4b7d8e50d16ed602d5f9b3f905a048f9a9
built_at=2026-08-09T19:43:11Z built_at=2026-08-14T17:41:17Z
base_path=/aiui/ base_path=/aiui/
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
.header-overlay-panel[data-v-cce6627c]{background:#000000e0}.picker-enter-active[data-v-cce6627c]{transition:all .2s cubic-bezier(.22,1,.36,1)}.picker-leave-active[data-v-cce6627c]{transition:all .15s ease-in}.picker-enter-from[data-v-cce6627c],.picker-leave-to[data-v-cce6627c]{opacity:0;transform:translateY(-8px)}.context-menu-enter-active[data-v-13d6c372]{transition:all .15s cubic-bezier(.22,1,.36,1)}.context-menu-leave-active[data-v-13d6c372]{transition:all .1s ease-in}.context-menu-enter-from[data-v-13d6c372],.context-menu-leave-to[data-v-13d6c372]{opacity:0;transform:scale(.95)}.settings-modal-enter-active[data-v-c97db749]{transition:opacity .2s ease-out}.settings-modal-enter-active .glass-card[data-v-c97db749]{transition:all .25s cubic-bezier(.22,1,.36,1)}.settings-modal-leave-active[data-v-c97db749]{transition:opacity .15s ease-in}.settings-modal-enter-from[data-v-c97db749],.settings-modal-leave-to[data-v-c97db749]{opacity:0} .header-overlay-panel[data-v-49f33a03]{background:#000000e0}.picker-enter-active[data-v-49f33a03]{transition:all .2s cubic-bezier(.22,1,.36,1)}.picker-leave-active[data-v-49f33a03]{transition:all .15s ease-in}.picker-enter-from[data-v-49f33a03],.picker-leave-to[data-v-49f33a03]{opacity:0;transform:translateY(-8px)}.context-menu-enter-active[data-v-13d6c372]{transition:all .15s cubic-bezier(.22,1,.36,1)}.context-menu-leave-active[data-v-13d6c372]{transition:all .1s ease-in}.context-menu-enter-from[data-v-13d6c372],.context-menu-leave-to[data-v-13d6c372]{opacity:0;transform:scale(.95)}.settings-modal-enter-active[data-v-c97db749]{transition:opacity .2s ease-out}.settings-modal-enter-active .glass-card[data-v-c97db749]{transition:all .25s cubic-bezier(.22,1,.36,1)}.settings-modal-leave-active[data-v-c97db749]{transition:opacity .15s ease-in}.settings-modal-enter-from[data-v-c97db749],.settings-modal-leave-to[data-v-c97db749]{opacity:0}
@@ -1,4 +1,4 @@
import{a as S,D as V,c as r,e as s,E as y,G as g,t as c,F as p,H as h,i as b,g as j,I as B,r as u,k as T,J as U,b as l,n as k}from"./index-8cIrvc8q.js";import{useNostr as E}from"./useNostr-XONW-p_l.js";const F={class:"min-h-screen bg-[#0a0a0a] text-white"},H={class:"sticky top-0 z-10 glass border-b border-white/5"},L={class:"max-w-3xl mx-auto px-4 py-3 flex items-center gap-3"},R={class:"flex-1 min-w-0"},z={class:"text-sm font-semibold text-white/90 truncate"},G={class:"text-xs text-white/40"},P={key:0,class:"flex items-center justify-center h-64"},J={key:1,class:"max-w-3xl mx-auto px-4 py-12 text-center"},Y={class:"text-white/40 text-sm"},q={key:2,class:"max-w-3xl mx-auto px-4 py-6 space-y-4"},K={class:"flex items-center gap-2 mb-2"},O=["textContent"],Z=S({__name:"ConversationViewerPage",setup(Q){const C=B(),{connect:N,fetchNote:A}=E(),v=u(!0),i=u(null),f=u("Shared Conversation"),x=u(null),d=u(null),w=u([]),I=T(()=>d.value?new Date(d.value*1e3).toLocaleDateString("en-US",{year:"numeric",month:"long",day:"numeric"}):"");function D(n){const t=[],o=n.split(` import{a as S,D as V,c as r,e as s,E as y,G as g,t as c,F as p,H as h,i as b,g as j,I as B,r as u,k as T,J as U,b as l,n as k}from"./index-DNCGxUDM.js";import{useNostr as E}from"./useNostr-CNDx2L4S.js";const F={class:"min-h-screen bg-[#0a0a0a] text-white"},H={class:"sticky top-0 z-10 glass border-b border-white/5"},L={class:"max-w-3xl mx-auto px-4 py-3 flex items-center gap-3"},R={class:"flex-1 min-w-0"},z={class:"text-sm font-semibold text-white/90 truncate"},G={class:"text-xs text-white/40"},P={key:0,class:"flex items-center justify-center h-64"},J={key:1,class:"max-w-3xl mx-auto px-4 py-12 text-center"},Y={class:"text-white/40 text-sm"},q={key:2,class:"max-w-3xl mx-auto px-4 py-6 space-y-4"},K={class:"flex items-center gap-2 mb-2"},O=["textContent"],Z=S({__name:"ConversationViewerPage",setup(Q){const C=B(),{connect:N,fetchNote:A}=E(),v=u(!0),i=u(null),f=u("Shared Conversation"),x=u(null),d=u(null),w=u([]),I=T(()=>d.value?new Date(d.value*1e3).toLocaleDateString("en-US",{year:"numeric",month:"long",day:"numeric"}):"");function D(n){const t=[],o=n.split(`
`);let e="",a=[];for(const m of o){const _=m.match(/^##?\s*(?:Human|User|You)/),M=m.match(/^##?\s*(?:Assistant|AI|Claude)/);_||M?(e&&a.length>0&&t.push({role:e,content:a.join(` `);let e="",a=[];for(const m of o){const _=m.match(/^##?\s*(?:Human|User|You)/),M=m.match(/^##?\s*(?:Assistant|AI|Claude)/);_||M?(e&&a.length>0&&t.push({role:e,content:a.join(`
`).trim()}),e=_?"user":"assistant",a=[]):a.push(m)}return e&&a.length>0&&t.push({role:e,content:a.join(` `).trim()}),e=_?"user":"assistant",a=[]):a.push(m)}return e&&a.length>0&&t.push({role:e,content:a.join(`
`).trim()}),t.length===0&&n.trim()&&t.push({role:"assistant",content:n.trim()}),t}return V(async()=>{try{const n=C.params.nostrAddr;if(!n){i.value="No Nostr address provided.";return}await N();let t=null;try{const e=atob(n).split(":");e.length>=2&&(t={dTag:e[0],pubkey:e[1]})}catch{}if(t){const o=await A(t.dTag);if(o){const e=o.tags.find(a=>a[0]==="title");e&&(f.value=e[1]),x.value=o.authorName??null,d.value=o.created_at,w.value=D(o.content)}else i.value="Conversation not found on relays."}else i.value="Invalid Nostr address format."}catch(n){i.value=n instanceof Error?n.message:"Failed to load conversation."}finally{v.value=!1}}),(n,t)=>{const o=U("router-link");return l(),r("div",F,[s("header",H,[s("div",L,[y(o,{to:"/",class:"text-white/40 hover:text-white/70 transition-colors"},{default:g(()=>[...t[0]||(t[0]=[s("svg",{class:"w-5 h-5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor","stroke-width":"2"},[s("path",{"stroke-linecap":"round","stroke-linejoin":"round",d:"M10 19l-7-7m0 0l7-7m-7 7h18"})],-1)])]),_:1}),s("div",R,[s("h1",z,c(f.value),1),s("p",G,[x.value?(l(),r(p,{key:0},[h("by "+c(x.value),1)],64)):b("",!0),d.value?(l(),r(p,{key:1},[h(" · "+c(I.value),1)],64)):b("",!0)])]),t[1]||(t[1]=s("span",{class:"text-xs px-2 py-1 rounded-full bg-white/5 text-white/40"},"Read-only",-1))])]),v.value?(l(),r("div",P,[...t[2]||(t[2]=[s("div",{class:"w-6 h-6 rounded-full border-2 border-accent/30 border-t-accent animate-spin"},null,-1)])])):i.value?(l(),r("div",J,[s("p",Y,c(i.value),1),y(o,{to:"/",class:"mt-4 inline-block text-accent text-sm hover:underline"},{default:g(()=>[...t[3]||(t[3]=[h(" Go to AIUI ",-1)])]),_:1})])):(l(),r("main",q,[(l(!0),r(p,null,j(w.value,(e,a)=>(l(),r("div",{key:a,class:k(["rounded-xl p-4",e.role==="user"?"bg-white/[0.03] border border-white/5 ml-8":"mr-8"])},[s("div",K,[s("span",{class:k(["text-xs font-bold uppercase tracking-wider",e.role==="user"?"text-accent/70":"text-white/30"])},c(e.role==="user"?"Human":"Assistant"),3)]),s("div",{class:"text-sm text-white/80 leading-relaxed whitespace-pre-wrap break-words",textContent:c(e.content)},null,8,O)],2))),128))])),t[4]||(t[4]=s("footer",{class:"max-w-3xl mx-auto px-4 py-8 text-center"},[s("p",{class:"text-xs text-white/20"}," Shared via AIUI · Powered by Nostr ")],-1))])}}});export{Z as default}; `).trim()}),t.length===0&&n.trim()&&t.push({role:"assistant",content:n.trim()}),t}return V(async()=>{try{const n=C.params.nostrAddr;if(!n){i.value="No Nostr address provided.";return}await N();let t=null;try{const e=atob(n).split(":");e.length>=2&&(t={dTag:e[0],pubkey:e[1]})}catch{}if(t){const o=await A(t.dTag);if(o){const e=o.tags.find(a=>a[0]==="title");e&&(f.value=e[1]),x.value=o.authorName??null,d.value=o.created_at,w.value=D(o.content)}else i.value="Conversation not found on relays."}else i.value="Invalid Nostr address format."}catch(n){i.value=n instanceof Error?n.message:"Failed to load conversation."}finally{v.value=!1}}),(n,t)=>{const o=U("router-link");return l(),r("div",F,[s("header",H,[s("div",L,[y(o,{to:"/",class:"text-white/40 hover:text-white/70 transition-colors"},{default:g(()=>[...t[0]||(t[0]=[s("svg",{class:"w-5 h-5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor","stroke-width":"2"},[s("path",{"stroke-linecap":"round","stroke-linejoin":"round",d:"M10 19l-7-7m0 0l7-7m-7 7h18"})],-1)])]),_:1}),s("div",R,[s("h1",z,c(f.value),1),s("p",G,[x.value?(l(),r(p,{key:0},[h("by "+c(x.value),1)],64)):b("",!0),d.value?(l(),r(p,{key:1},[h(" · "+c(I.value),1)],64)):b("",!0)])]),t[1]||(t[1]=s("span",{class:"text-xs px-2 py-1 rounded-full bg-white/5 text-white/40"},"Read-only",-1))])]),v.value?(l(),r("div",P,[...t[2]||(t[2]=[s("div",{class:"w-6 h-6 rounded-full border-2 border-accent/30 border-t-accent animate-spin"},null,-1)])])):i.value?(l(),r("div",J,[s("p",Y,c(i.value),1),y(o,{to:"/",class:"mt-4 inline-block text-accent text-sm hover:underline"},{default:g(()=>[...t[3]||(t[3]=[h(" Go to AIUI ",-1)])]),_:1})])):(l(),r("main",q,[(l(!0),r(p,null,j(w.value,(e,a)=>(l(),r("div",{key:a,class:k(["rounded-xl p-4",e.role==="user"?"bg-white/[0.03] border border-white/5 ml-8":"mr-8"])},[s("div",K,[s("span",{class:k(["text-xs font-bold uppercase tracking-wider",e.role==="user"?"text-accent/70":"text-white/30"])},c(e.role==="user"?"Human":"Assistant"),3)]),s("div",{class:"text-sm text-white/80 leading-relaxed whitespace-pre-wrap break-words",textContent:c(e.content)},null,8,O)],2))),128))])),t[4]||(t[4]=s("footer",{class:"max-w-3xl mx-auto px-4 py-8 text-center"},[s("p",{class:"text-xs text-white/20"}," Shared via AIUI · Powered by Nostr ")],-1))])}}});export{Z as default};
@@ -1 +1 @@
import{_ as m}from"./FilmDetail.vue_vue_type_script_setup_true_lang-BhKlPG3Y.js";import"./index-8cIrvc8q.js";export{m as default}; import{_ as m}from"./FilmDetail.vue_vue_type_script_setup_true_lang-Cgf4f-Ng.js";import"./index-DNCGxUDM.js";export{m as default};
-1
View File
@@ -1 +0,0 @@
import{_ as o}from"./FilmGrid.vue_vue_type_script_setup_true_lang-Dj0SEfcW.js";import"./index-8cIrvc8q.js";import"./useContentImages-7wLVntsF.js";export{o as default};
+1
View File
@@ -0,0 +1 @@
import{_ as o}from"./FilmGrid.vue_vue_type_script_setup_true_lang-CkIQ4bRp.js";import"./index-DNCGxUDM.js";import"./useContentImages-DdjyABL9.js";export{o as default};
@@ -1 +1 @@
import{a as F,b as l,c as r,e as o,n as d,u as a,t as c,f as L,w as S,v as j,F as v,g as m,h as U,i as u,j as E,r as y,k as w,l as z,m as B,p as D}from"./index-8cIrvc8q.js";import{u as G}from"./useContentImages-7wLVntsF.js";const N={class:"h-full flex flex-col"},V={class:"flex items-center justify-between gap-2"},I={class:"flex items-center gap-2 shrink-0"},M={class:"flex flex-wrap gap-1.5"},R=["onClick"],T={class:"flex-1 overflow-y-auto custom-scrollbar px-4 pt-4 pb-16"},q={class:"grid grid-cols-2 sm:grid-cols-3 gap-4"},P=["aria-label","onClick"],A={class:"poster-card flex-1 min-h-0"},H={key:0,class:"absolute inset-0 animate-shimmer"},J=["src","alt","onError"],K=["src","alt"],O={key:3,class:"absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none"},Q={class:"absolute bottom-0 left-0 right-0 p-2"},W={class:"text-xs font-semibold text-white/90 leading-tight truncate"},X={class:"flex items-center gap-1 mt-0.5"},Y={key:0,class:"text-xs text-accent font-bold"},Z={key:1,class:"text-xs text-white/40"},ee={class:"absolute top-1.5 right-1.5 flex gap-0.5"},te={key:0,class:"flex items-center justify-center py-12"},ae=F({__name:"FilmGrid",props:{films:{},title:{default:"Recommended Films"}},emits:["selectFilm"],setup(_){const b=_,{isDark:n}=E(),p=y(""),h=y(null),{coverSrc:x,fallbackSrc:k,onError:C,isLoading:f}=G({items:D(b,"films"),id:t=>t.id,existingUrl:t=>t.posterUrl||t.backdropUrl,fetch:t=>B(t.title,t.year).then(s=>s.posterUrl),fallback:t=>z(t.title,t.year)}),$=w(()=>{const t=new Map;for(const s of b.films)for(const e of s.genres)t.set(e,(t.get(e)??0)+1);return[...t.entries()].sort((s,e)=>e[1]-s[1]).slice(0,8).map(([s])=>s)}),g=w(()=>{let t=b.films;if(p.value){const s=p.value.toLowerCase();t=t.filter(e=>e.title.toLowerCase().includes(s)||e.director.toLowerCase().includes(s)||e.cast.some(i=>i.toLowerCase().includes(s)))}return h.value&&(t=t.filter(s=>s.genres.includes(h.value))),t});return(t,s)=>(l(),r("div",N,[o("div",{class:"p-4 space-y-3",style:U(a(n)?"border-bottom: 1px solid rgba(255, 255, 255, 0.08)":"border-bottom: 1px solid rgba(0, 0, 0, 0.06)")},[o("div",V,[o("h3",{class:d(["text-sm font-bold",a(n)?"text-white/90":"text-gray-900"])},c(_.title),3),o("div",I,[o("span",{class:d(["text-xs font-mono",a(n)?"text-white/30":"text-gray-400"])},c(g.value.length)+" films ",3),L(t.$slots,"header-actions")])]),S(o("input",{"onUpdate:modelValue":s[0]||(s[0]=e=>p.value=e),type:"text",placeholder:"Search films...",class:d(["w-full px-3 py-2 rounded-lg text-base outline-none transition-colors",a(n)?"bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10":"bg-black/3 text-gray-800 placeholder:text-gray-400 focus:bg-black/5"])},null,2),[[j,p.value]]),o("div",M,[(l(!0),r(v,null,m($.value,e=>(l(),r("button",{key:e,class:d(["text-xs px-2 py-1 rounded-md transition-all duration-150",h.value===e?"nav-tab-active":a(n)?"text-white/40 hover:text-white/70 hover:bg-white/5":"text-gray-500 hover:text-gray-800 hover:bg-black/5"]),onClick:i=>h.value=h.value===e?null:e},c(e),11,R))),128))])],4),o("div",T,[o("div",q,[(l(!0),r(v,null,m(g.value,e=>(l(),r("button",{key:e.id,class:"group flex flex-col items-stretch text-left w-full path-glass-bubble rounded-2xl overflow-hidden transition-all duration-200 hover:brightness-105","aria-label":`${e.title} (${e.year})`,onClick:i=>t.$emit("selectFilm",e)},[o("div",A,[o("div",{class:d(["aspect-[2/3] relative w-full overflow-hidden rounded-[10px]",a(x)(e)?"":a(n)?"bg-white/[0.06]":"bg-black/[0.04]"])},[a(f)(e)?(l(),r("div",H)):u("",!0),a(x)(e)?(l(),r("img",{key:1,src:a(x)(e),alt:`${e.title} (${e.year}) directed by ${e.director}`,class:"w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",loading:"lazy",onError:i=>a(C)(e)},null,40,J)):a(f)(e)?u("",!0):(l(),r("img",{key:2,src:a(k)(e),alt:e.title,class:"w-full h-full object-cover"},null,8,K)),a(x)(e)?(l(),r("div",O)):u("",!0),o("div",Q,[o("p",W,c(e.title),1),o("div",X,[e.rating>0?(l(),r("span",Y,"★ "+c(e.rating),1)):u("",!0),e.year>0?(l(),r("span",Z,c(e.year),1)):u("",!0)])]),o("div",ee,[(l(!0),r(v,null,m(e.sources.slice(0,2),i=>(l(),r("span",{key:i.type,class:"text-xs px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"},c(i.type),1))),128))])],2)])],8,P))),128))]),g.value.length===0?(l(),r("div",te,[o("p",{class:d(["text-sm",a(n)?"text-white/30":"text-gray-400"])}," No films match your search ",2)])):u("",!0)])]))}});export{ae as _}; import{a as F,b as l,c as r,e as o,n as d,u as a,t as c,f as L,w as S,v as j,F as v,g as m,h as U,i as u,j as E,r as y,k as w,l as z,m as B,p as D}from"./index-DNCGxUDM.js";import{u as G}from"./useContentImages-DdjyABL9.js";const N={class:"h-full flex flex-col"},V={class:"flex items-center justify-between gap-2"},I={class:"flex items-center gap-2 shrink-0"},M={class:"flex flex-wrap gap-1.5"},R=["onClick"],T={class:"flex-1 overflow-y-auto custom-scrollbar px-4 pt-4 pb-16"},q={class:"grid grid-cols-2 sm:grid-cols-3 gap-4"},P=["aria-label","onClick"],A={class:"poster-card flex-1 min-h-0"},H={key:0,class:"absolute inset-0 animate-shimmer"},J=["src","alt","onError"],K=["src","alt"],O={key:3,class:"absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none"},Q={class:"absolute bottom-0 left-0 right-0 p-2"},W={class:"text-xs font-semibold text-white/90 leading-tight truncate"},X={class:"flex items-center gap-1 mt-0.5"},Y={key:0,class:"text-xs text-accent font-bold"},Z={key:1,class:"text-xs text-white/40"},ee={class:"absolute top-1.5 right-1.5 flex gap-0.5"},te={key:0,class:"flex items-center justify-center py-12"},ae=F({__name:"FilmGrid",props:{films:{},title:{default:"Recommended Films"}},emits:["selectFilm"],setup(_){const b=_,{isDark:n}=E(),p=y(""),h=y(null),{coverSrc:x,fallbackSrc:k,onError:C,isLoading:f}=G({items:D(b,"films"),id:t=>t.id,existingUrl:t=>t.posterUrl||t.backdropUrl,fetch:t=>B(t.title,t.year).then(s=>s.posterUrl),fallback:t=>z(t.title,t.year)}),$=w(()=>{const t=new Map;for(const s of b.films)for(const e of s.genres)t.set(e,(t.get(e)??0)+1);return[...t.entries()].sort((s,e)=>e[1]-s[1]).slice(0,8).map(([s])=>s)}),g=w(()=>{let t=b.films;if(p.value){const s=p.value.toLowerCase();t=t.filter(e=>e.title.toLowerCase().includes(s)||e.director.toLowerCase().includes(s)||e.cast.some(i=>i.toLowerCase().includes(s)))}return h.value&&(t=t.filter(s=>s.genres.includes(h.value))),t});return(t,s)=>(l(),r("div",N,[o("div",{class:"p-4 space-y-3",style:U(a(n)?"border-bottom: 1px solid rgba(255, 255, 255, 0.08)":"border-bottom: 1px solid rgba(0, 0, 0, 0.06)")},[o("div",V,[o("h3",{class:d(["text-sm font-bold",a(n)?"text-white/90":"text-gray-900"])},c(_.title),3),o("div",I,[o("span",{class:d(["text-xs font-mono",a(n)?"text-white/30":"text-gray-400"])},c(g.value.length)+" films ",3),L(t.$slots,"header-actions")])]),S(o("input",{"onUpdate:modelValue":s[0]||(s[0]=e=>p.value=e),type:"text",placeholder:"Search films...",class:d(["w-full px-3 py-2 rounded-lg text-base outline-none transition-colors",a(n)?"bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10":"bg-black/3 text-gray-800 placeholder:text-gray-400 focus:bg-black/5"])},null,2),[[j,p.value]]),o("div",M,[(l(!0),r(v,null,m($.value,e=>(l(),r("button",{key:e,class:d(["text-xs px-2 py-1 rounded-md transition-all duration-150",h.value===e?"nav-tab-active":a(n)?"text-white/40 hover:text-white/70 hover:bg-white/5":"text-gray-500 hover:text-gray-800 hover:bg-black/5"]),onClick:i=>h.value=h.value===e?null:e},c(e),11,R))),128))])],4),o("div",T,[o("div",q,[(l(!0),r(v,null,m(g.value,e=>(l(),r("button",{key:e.id,class:"group flex flex-col items-stretch text-left w-full path-glass-bubble rounded-2xl overflow-hidden transition-all duration-200 hover:brightness-105","aria-label":`${e.title} (${e.year})`,onClick:i=>t.$emit("selectFilm",e)},[o("div",A,[o("div",{class:d(["aspect-[2/3] relative w-full overflow-hidden rounded-[10px]",a(x)(e)?"":a(n)?"bg-white/[0.06]":"bg-black/[0.04]"])},[a(f)(e)?(l(),r("div",H)):u("",!0),a(x)(e)?(l(),r("img",{key:1,src:a(x)(e),alt:`${e.title} (${e.year}) directed by ${e.director}`,class:"w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",loading:"lazy",onError:i=>a(C)(e)},null,40,J)):a(f)(e)?u("",!0):(l(),r("img",{key:2,src:a(k)(e),alt:e.title,class:"w-full h-full object-cover"},null,8,K)),a(x)(e)?(l(),r("div",O)):u("",!0),o("div",Q,[o("p",W,c(e.title),1),o("div",X,[e.rating>0?(l(),r("span",Y,"★ "+c(e.rating),1)):u("",!0),e.year>0?(l(),r("span",Z,c(e.year),1)):u("",!0)])]),o("div",ee,[(l(!0),r(v,null,m(e.sources.slice(0,2),i=>(l(),r("span",{key:i.type,class:"text-xs px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"},c(i.type),1))),128))])],2)])],8,P))),128))]),g.value.length===0?(l(),r("div",te,[o("p",{class:d(["text-sm",a(n)?"text-white/30":"text-gray-400"])}," No films match your search ",2)])):u("",!0)])]))}});export{ae as _};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{_ as m}from"./SongDetail.vue_vue_type_script_setup_true_lang-B3om3Z8v.js";import"./index-8cIrvc8q.js";export{m as default}; import{_ as m}from"./SongDetail.vue_vue_type_script_setup_true_lang-0mQhUBE8.js";import"./index-DNCGxUDM.js";export{m as default};
+1
View File
@@ -0,0 +1 @@
import{_ as o}from"./SongGrid.vue_vue_type_script_setup_true_lang-70SttNTZ.js";import"./index-DNCGxUDM.js";import"./useContentImages-DdjyABL9.js";export{o as default};
-1
View File
@@ -1 +0,0 @@
import{_ as o}from"./SongGrid.vue_vue_type_script_setup_true_lang-IvAOIQYW.js";import"./index-8cIrvc8q.js";import"./useContentImages-7wLVntsF.js";export{o as default};
@@ -1 +1 @@
import{a as z,q as M,x as B,y as E,p as q,b as o,c as r,e as s,n as c,u as a,t as u,f as D,w as F,v as G,F as v,g as m,h as N,i as h,z as U,j as V,r as _,k}from"./index-8cIrvc8q.js";import{u as P}from"./useContentImages-7wLVntsF.js";const R={class:"h-full flex flex-col"},T={class:"flex items-center justify-between gap-2"},I={class:"flex items-center gap-2 shrink-0"},A={class:"flex flex-wrap gap-1.5"},H=["onClick"],J={class:"flex-1 overflow-y-auto custom-scrollbar px-4 pt-4 pb-16"},K={class:"grid grid-cols-2 sm:grid-cols-3 gap-4"},O=["aria-label","onClick"],Q={class:"cover-card flex-1 min-h-0 relative flex items-center justify-center"},W={key:0,class:"absolute inset-0 animate-shimmer"},X=["onClick"],Y=["src","alt","onError"],Z=["src","alt"],tt={key:3,class:"absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none"},et={class:"absolute bottom-0 left-0 right-0 p-2"},st={class:"text-xs font-semibold text-white/90 leading-tight truncate"},lt={key:0,class:"text-xs text-white/40 truncate mt-0.5"},at={class:"absolute top-1.5 right-1.5 flex gap-0.5 flex-wrap justify-end max-w-[60%]"},ot={key:0,class:"flex items-center justify-center py-12"},nt=z({__name:"SongGrid",props:{songs:{},title:{default:"Recommended Songs"}},emits:["selectSong"],setup(g,{emit:C}){const x=g,y=C,{isDark:i}=V(),{play:S}=M(),p=_(""),d=_(null),{coverSrc:f,fallbackSrc:j,onError:$,isLoading:w}=P({items:q(x,"songs"),id:e=>e.id,existingUrl:e=>e.coverUrl,fetch:e=>E(e.title,e.artist,e.album),fallback:e=>B(e.title,e.artist)}),L=k(()=>{const e=new Map;for(const l of x.songs)for(const t of l.genres??[])e.set(t,(e.get(t)??0)+1);return[...e.entries()].sort((l,t)=>t[1]-l[1]).slice(0,8).map(([l])=>l)}),b=k(()=>{let e=x.songs;if(p.value){const l=p.value.toLowerCase();e=e.filter(t=>t.title.toLowerCase().includes(l)||t.artist.toLowerCase().includes(l)||(t.album??"").toLowerCase().includes(l))}return d.value&&(e=e.filter(l=>(l.genres??[]).includes(d.value))),e});return(e,l)=>(o(),r("div",R,[s("div",{class:"p-4 space-y-3",style:N(a(i)?"border-bottom: 1px solid rgba(255, 255, 255, 0.08)":"border-bottom: 1px solid rgba(0, 0, 0, 0.06)")},[s("div",T,[s("h3",{class:c(["text-sm font-bold",a(i)?"text-white/90":"text-gray-900"])},u(g.title),3),s("div",I,[s("span",{class:c(["text-xs font-mono",a(i)?"text-white/30":"text-gray-400"])},u(b.value.length)+" songs ",3),D(e.$slots,"header-actions")])]),F(s("input",{"onUpdate:modelValue":l[0]||(l[0]=t=>p.value=t),type:"text",placeholder:"Search songs...",class:c(["w-full px-3 py-2 rounded-lg text-base outline-none transition-colors",a(i)?"bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10":"bg-black/3 text-gray-800 placeholder:text-gray-400 focus:bg-black/5"])},null,2),[[G,p.value]]),s("div",A,[(o(!0),r(v,null,m(L.value,t=>(o(),r("button",{key:t,class:c(["text-xs px-2 py-1 rounded-md transition-all duration-150",d.value===t?"nav-tab-active":a(i)?"text-white/40 hover:text-white/70 hover:bg-white/5":"text-gray-500 hover:text-gray-800 hover:bg-black/5"]),onClick:n=>d.value=d.value===t?null:t},u(t),11,H))),128))])],4),s("div",J,[s("div",K,[(o(!0),r(v,null,m(b.value,t=>(o(),r("button",{key:t.id,class:"group flex flex-col items-stretch text-left w-full path-glass-bubble rounded-2xl overflow-hidden transition-all duration-200 hover:brightness-105","aria-label":`${t.title} by ${t.artist}`,onClick:n=>y("selectSong",t)},[s("div",Q,[s("div",{class:c(["aspect-square relative w-full overflow-hidden rounded-[10px]",a(f)(t)?"":a(i)?"bg-white/[0.06]":"bg-black/[0.04]"])},[a(w)(t)?(o(),r("div",W)):h("",!0),s("button",{class:"absolute inset-0 flex items-center justify-center z-10 backdrop-blur-sm bg-black/30 opacity-0 group-hover:opacity-100 transition-all duration-200","aria-label":"Play",onClick:U(n=>{a(S)(t),y("selectSong",t)},["stop"])},[...l[1]||(l[1]=[s("span",{class:"w-16 h-16 rounded-full flex items-center justify-center path-glass-icon"},[s("svg",{class:"w-8 h-8 text-white",fill:"currentColor",viewBox:"0 0 24 24"},[s("path",{d:"M8 5v14l11-7L8 5z"})])],-1)])],8,X),a(f)(t)?(o(),r("img",{key:1,src:a(f)(t),alt:`${t.title} by ${t.artist}`,class:"w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",loading:"lazy",onError:n=>a($)(t)},null,40,Y)):a(w)(t)?h("",!0):(o(),r("img",{key:2,src:a(j)(t),alt:t.title,class:"w-full h-full object-cover"},null,8,Z)),a(f)(t)?(o(),r("div",tt)):h("",!0),s("div",et,[s("p",st,u(t.title),1),t.artist?(o(),r("p",lt,u(t.artist),1)):h("",!0)]),s("div",at,[(o(!0),r(v,null,m((t.sources??[]).slice(0,2),n=>(o(),r("span",{key:n.type,class:"text-xs px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"},u(n.type),1))),128))])],2)])],8,O))),128))]),b.value.length===0?(o(),r("div",ot,[s("p",{class:c(["text-sm",a(i)?"text-white/30":"text-gray-400"])}," No songs match your search ",2)])):h("",!0)])]))}});export{nt as _}; import{a as z,q as M,x as B,y as E,p as q,b as o,c as r,e as s,n as c,u as a,t as u,f as D,w as F,v as G,F as v,g as m,h as N,i as h,z as U,j as V,r as _,k}from"./index-DNCGxUDM.js";import{u as P}from"./useContentImages-DdjyABL9.js";const R={class:"h-full flex flex-col"},T={class:"flex items-center justify-between gap-2"},I={class:"flex items-center gap-2 shrink-0"},A={class:"flex flex-wrap gap-1.5"},H=["onClick"],J={class:"flex-1 overflow-y-auto custom-scrollbar px-4 pt-4 pb-16"},K={class:"grid grid-cols-2 sm:grid-cols-3 gap-4"},O=["aria-label","onClick"],Q={class:"cover-card flex-1 min-h-0 relative flex items-center justify-center"},W={key:0,class:"absolute inset-0 animate-shimmer"},X=["onClick"],Y=["src","alt","onError"],Z=["src","alt"],tt={key:3,class:"absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none"},et={class:"absolute bottom-0 left-0 right-0 p-2"},st={class:"text-xs font-semibold text-white/90 leading-tight truncate"},lt={key:0,class:"text-xs text-white/40 truncate mt-0.5"},at={class:"absolute top-1.5 right-1.5 flex gap-0.5 flex-wrap justify-end max-w-[60%]"},ot={key:0,class:"flex items-center justify-center py-12"},nt=z({__name:"SongGrid",props:{songs:{},title:{default:"Recommended Songs"}},emits:["selectSong"],setup(g,{emit:C}){const x=g,y=C,{isDark:i}=V(),{play:S}=M(),p=_(""),d=_(null),{coverSrc:f,fallbackSrc:j,onError:$,isLoading:w}=P({items:q(x,"songs"),id:e=>e.id,existingUrl:e=>e.coverUrl,fetch:e=>E(e.title,e.artist,e.album),fallback:e=>B(e.title,e.artist)}),L=k(()=>{const e=new Map;for(const l of x.songs)for(const t of l.genres??[])e.set(t,(e.get(t)??0)+1);return[...e.entries()].sort((l,t)=>t[1]-l[1]).slice(0,8).map(([l])=>l)}),b=k(()=>{let e=x.songs;if(p.value){const l=p.value.toLowerCase();e=e.filter(t=>t.title.toLowerCase().includes(l)||t.artist.toLowerCase().includes(l)||(t.album??"").toLowerCase().includes(l))}return d.value&&(e=e.filter(l=>(l.genres??[]).includes(d.value))),e});return(e,l)=>(o(),r("div",R,[s("div",{class:"p-4 space-y-3",style:N(a(i)?"border-bottom: 1px solid rgba(255, 255, 255, 0.08)":"border-bottom: 1px solid rgba(0, 0, 0, 0.06)")},[s("div",T,[s("h3",{class:c(["text-sm font-bold",a(i)?"text-white/90":"text-gray-900"])},u(g.title),3),s("div",I,[s("span",{class:c(["text-xs font-mono",a(i)?"text-white/30":"text-gray-400"])},u(b.value.length)+" songs ",3),D(e.$slots,"header-actions")])]),F(s("input",{"onUpdate:modelValue":l[0]||(l[0]=t=>p.value=t),type:"text",placeholder:"Search songs...",class:c(["w-full px-3 py-2 rounded-lg text-base outline-none transition-colors",a(i)?"bg-white/5 text-white/80 placeholder:text-white/25 focus:bg-white/10":"bg-black/3 text-gray-800 placeholder:text-gray-400 focus:bg-black/5"])},null,2),[[G,p.value]]),s("div",A,[(o(!0),r(v,null,m(L.value,t=>(o(),r("button",{key:t,class:c(["text-xs px-2 py-1 rounded-md transition-all duration-150",d.value===t?"nav-tab-active":a(i)?"text-white/40 hover:text-white/70 hover:bg-white/5":"text-gray-500 hover:text-gray-800 hover:bg-black/5"]),onClick:n=>d.value=d.value===t?null:t},u(t),11,H))),128))])],4),s("div",J,[s("div",K,[(o(!0),r(v,null,m(b.value,t=>(o(),r("button",{key:t.id,class:"group flex flex-col items-stretch text-left w-full path-glass-bubble rounded-2xl overflow-hidden transition-all duration-200 hover:brightness-105","aria-label":`${t.title} by ${t.artist}`,onClick:n=>y("selectSong",t)},[s("div",Q,[s("div",{class:c(["aspect-square relative w-full overflow-hidden rounded-[10px]",a(f)(t)?"":a(i)?"bg-white/[0.06]":"bg-black/[0.04]"])},[a(w)(t)?(o(),r("div",W)):h("",!0),s("button",{class:"absolute inset-0 flex items-center justify-center z-10 backdrop-blur-sm bg-black/30 opacity-0 group-hover:opacity-100 transition-all duration-200","aria-label":"Play",onClick:U(n=>{a(S)(t),y("selectSong",t)},["stop"])},[...l[1]||(l[1]=[s("span",{class:"w-16 h-16 rounded-full flex items-center justify-center path-glass-icon"},[s("svg",{class:"w-8 h-8 text-white",fill:"currentColor",viewBox:"0 0 24 24"},[s("path",{d:"M8 5v14l11-7L8 5z"})])],-1)])],8,X),a(f)(t)?(o(),r("img",{key:1,src:a(f)(t),alt:`${t.title} by ${t.artist}`,class:"w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",loading:"lazy",onError:n=>a($)(t)},null,40,Y)):a(w)(t)?h("",!0):(o(),r("img",{key:2,src:a(j)(t),alt:t.title,class:"w-full h-full object-cover"},null,8,Z)),a(f)(t)?(o(),r("div",tt)):h("",!0),s("div",et,[s("p",st,u(t.title),1),t.artist?(o(),r("p",lt,u(t.artist),1)):h("",!0)]),s("div",at,[(o(!0),r(v,null,m((t.sources??[]).slice(0,2),n=>(o(),r("span",{key:n.type,class:"text-xs px-1 py-0.5 rounded bg-black/60 text-white/70 backdrop-blur-sm"},u(n.type),1))),128))])],2)])],8,O))),128))]),b.value.length===0?(o(),r("div",ot,[s("p",{class:c(["text-sm",a(i)?"text-white/30":"text-gray-400"])}," No songs match your search ",2)])):h("",!0)])]))}});export{nt as _};
@@ -1 +1 @@
import{a as h,J as m,b as d,c as r,e as t,t as s,F as u,g as p,K as x,h as f}from"./index-8cIrvc8q.js";const g={class:"rounded-lg bg-white/[0.03] border border-white/5 p-2.5 mb-1"},b={class:"flex items-center gap-1.5 mb-1"},w={class:"w-5 h-5 rounded-full shrink-0 flex items-center justify-center text-xs font-bold bg-purple-500/20 text-purple-400"},y={class:"text-xs font-semibold text-white/70"},v={class:"text-xs ml-auto text-white/20"},k={class:"text-xs text-white/60 leading-relaxed whitespace-pre-wrap"},T=h({__name:"ThreadNode",props:{node:{},depth:{}},emits:["reply"],setup(e){function i(o){return new Date(o*1e3).toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit"})}return(o,n)=>{const l=m("ThreadNode",!0);return d(),r("div",{style:f({paddingLeft:`${Math.min(e.depth,4)*16}px`})},[t("div",g,[t("div",b,[t("div",w,s(e.node.note.authorName?.charAt(0)?.toUpperCase()??"?"),1),t("span",y,s(e.node.note.authorName??"anon"),1),t("span",v,s(i(e.node.note.created_at)),1)]),t("p",k,s(e.node.note.content),1),t("button",{class:"text-xs text-white/25 hover:text-accent/60 mt-1 transition-colors",onClick:n[0]||(n[0]=a=>o.$emit("reply",e.node.note))}," Reply ")]),(d(!0),r(u,null,p(e.node.children,a=>(d(),x(l,{key:a.note.id,node:a,depth:e.depth+1,onReply:n[1]||(n[1]=c=>o.$emit("reply",c))},null,8,["node","depth"]))),128))],4)}}});export{T as default}; import{a as h,J as m,b as d,c as r,e as t,t as s,F as u,g as p,K as x,h as f}from"./index-DNCGxUDM.js";const g={class:"rounded-lg bg-white/[0.03] border border-white/5 p-2.5 mb-1"},b={class:"flex items-center gap-1.5 mb-1"},w={class:"w-5 h-5 rounded-full shrink-0 flex items-center justify-center text-xs font-bold bg-purple-500/20 text-purple-400"},y={class:"text-xs font-semibold text-white/70"},v={class:"text-xs ml-auto text-white/20"},k={class:"text-xs text-white/60 leading-relaxed whitespace-pre-wrap"},T=h({__name:"ThreadNode",props:{node:{},depth:{}},emits:["reply"],setup(e){function i(o){return new Date(o*1e3).toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit"})}return(o,n)=>{const l=m("ThreadNode",!0);return d(),r("div",{style:f({paddingLeft:`${Math.min(e.depth,4)*16}px`})},[t("div",g,[t("div",b,[t("div",w,s(e.node.note.authorName?.charAt(0)?.toUpperCase()??"?"),1),t("span",y,s(e.node.note.authorName??"anon"),1),t("span",v,s(i(e.node.note.created_at)),1)]),t("p",k,s(e.node.note.content),1),t("button",{class:"text-xs text-white/25 hover:text-accent/60 mt-1 transition-colors",onClick:n[0]||(n[0]=a=>o.$emit("reply",e.node.note))}," Reply ")]),(d(!0),r(u,null,p(e.node.children,a=>(d(),x(l,{key:a.note.id,node:a,depth:e.depth+1,onReply:n[1]||(n[1]=c=>o.$emit("reply",c))},null,8,["node","depth"]))),128))],4)}}});export{T as default};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{e as x,c as O,g as m,k as P,h as p,j as w,l as c,m as A,n as I,t as N,o as E}from"./_baseUniq-DAOs4kUj.js";import{aR as g,ar as F,aS as M,aT as T,aU as _,aV as l,aW as $,aX as B,aY as S,aZ as y}from"./mermaid.core-v0oo9NRr.js";var R=/\s/;function G(n){for(var r=n.length;r--&&R.test(n.charAt(r)););return r}var H=/^\s+/;function L(n){return n&&n.slice(0,G(n)+1).replace(H,"")}var o=NaN,W=/^[-+]0x[0-9a-f]+$/i,X=/^0b[01]+$/i,Y=/^0o[0-7]+$/i,q=parseInt;function z(n){if(typeof n=="number")return n;if(x(n))return o;if(g(n)){var r=typeof n.valueOf=="function"?n.valueOf():n;n=g(r)?r+"":r}if(typeof n!="string")return n===0?n:+n;n=L(n);var t=X.test(n);return t||Y.test(n)?q(n.slice(2),t?2:8):W.test(n)?o:+n}var v=1/0,C=17976931348623157e292;function K(n){if(!n)return n===0?n:0;if(n=z(n),n===v||n===-v){var r=n<0?-1:1;return r*C}return n===n?n:0}function U(n){var r=K(n),t=r%1;return r===r?t?r-t:r:0}function fn(n){var r=n==null?0:n.length;return r?O(n):[]}var b=Object.prototype,Z=b.hasOwnProperty,dn=F(function(n,r){n=Object(n);var t=-1,i=r.length,a=i>2?r[2]:void 0;for(a&&M(r[0],r[1],a)&&(i=1);++t<i;)for(var f=r[t],e=T(f),s=-1,d=e.length;++s<d;){var u=e[s],h=n[u];(h===void 0||_(h,b[u])&&!Z.call(n,u))&&(n[u]=f[u])}return n});function un(n){var r=n==null?0:n.length;return r?n[r-1]:void 0}function D(n){return function(r,t,i){var a=Object(r);if(!l(r)){var f=m(t);r=P(r),t=function(s){return f(a[s],s,a)}}var e=n(r,t,i);return e>-1?a[f?r[e]:e]:void 0}}var J=Math.max;function Q(n,r,t){var i=n==null?0:n.length;if(!i)return-1;var a=t==null?0:U(t);return a<0&&(a=J(i+a,0)),p(n,m(r),a)}var hn=D(Q);function V(n,r){var t=-1,i=l(n)?Array(n.length):[];return w(n,function(a,f,e){i[++t]=r(a,f,e)}),i}function gn(n,r){var t=$(n)?c:V;return t(n,m(r))}var j=Object.prototype,k=j.hasOwnProperty;function nn(n,r){return n!=null&&k.call(n,r)}function mn(n,r){return n!=null&&A(n,r,nn)}function rn(n,r){return n<r}function tn(n,r,t){for(var i=-1,a=n.length;++i<a;){var f=n[i],e=r(f);if(e!=null&&(s===void 0?e===e&&!x(e):t(e,s)))var s=e,d=f}return d}function on(n){return n&&n.length?tn(n,B,rn):void 0}function an(n,r,t,i){if(!g(n))return n;r=I(r,n);for(var a=-1,f=r.length,e=f-1,s=n;s!=null&&++a<f;){var d=N(r[a]),u=t;if(d==="__proto__"||d==="constructor"||d==="prototype")return n;if(a!=e){var h=s[d];u=void 0,u===void 0&&(u=g(h)?h:S(r[a+1])?[]:{})}y(s,d,u),s=s[d]}return n}function vn(n,r,t){for(var i=-1,a=r.length,f={};++i<a;){var e=r[i],s=E(n,e);t(s,e)&&an(f,I(e,n),s)}return f}export{rn as a,tn as b,V as c,vn as d,on as e,fn as f,hn as g,mn as h,dn as i,U as j,un as l,gn as m,K as t}; import{e as x,c as O,g as m,k as P,h as p,j as w,l as c,m as A,n as I,t as N,o as E}from"./_baseUniq-T1y-Xwdr.js";import{aR as g,ar as F,aS as M,aT as T,aU as _,aV as l,aW as $,aX as B,aY as S,aZ as y}from"./mermaid.core-CFUktQ8s.js";var R=/\s/;function G(n){for(var r=n.length;r--&&R.test(n.charAt(r)););return r}var H=/^\s+/;function L(n){return n&&n.slice(0,G(n)+1).replace(H,"")}var o=NaN,W=/^[-+]0x[0-9a-f]+$/i,X=/^0b[01]+$/i,Y=/^0o[0-7]+$/i,q=parseInt;function z(n){if(typeof n=="number")return n;if(x(n))return o;if(g(n)){var r=typeof n.valueOf=="function"?n.valueOf():n;n=g(r)?r+"":r}if(typeof n!="string")return n===0?n:+n;n=L(n);var t=X.test(n);return t||Y.test(n)?q(n.slice(2),t?2:8):W.test(n)?o:+n}var v=1/0,C=17976931348623157e292;function K(n){if(!n)return n===0?n:0;if(n=z(n),n===v||n===-v){var r=n<0?-1:1;return r*C}return n===n?n:0}function U(n){var r=K(n),t=r%1;return r===r?t?r-t:r:0}function fn(n){var r=n==null?0:n.length;return r?O(n):[]}var b=Object.prototype,Z=b.hasOwnProperty,dn=F(function(n,r){n=Object(n);var t=-1,i=r.length,a=i>2?r[2]:void 0;for(a&&M(r[0],r[1],a)&&(i=1);++t<i;)for(var f=r[t],e=T(f),s=-1,d=e.length;++s<d;){var u=e[s],h=n[u];(h===void 0||_(h,b[u])&&!Z.call(n,u))&&(n[u]=f[u])}return n});function un(n){var r=n==null?0:n.length;return r?n[r-1]:void 0}function D(n){return function(r,t,i){var a=Object(r);if(!l(r)){var f=m(t);r=P(r),t=function(s){return f(a[s],s,a)}}var e=n(r,t,i);return e>-1?a[f?r[e]:e]:void 0}}var J=Math.max;function Q(n,r,t){var i=n==null?0:n.length;if(!i)return-1;var a=t==null?0:U(t);return a<0&&(a=J(i+a,0)),p(n,m(r),a)}var hn=D(Q);function V(n,r){var t=-1,i=l(n)?Array(n.length):[];return w(n,function(a,f,e){i[++t]=r(a,f,e)}),i}function gn(n,r){var t=$(n)?c:V;return t(n,m(r))}var j=Object.prototype,k=j.hasOwnProperty;function nn(n,r){return n!=null&&k.call(n,r)}function mn(n,r){return n!=null&&A(n,r,nn)}function rn(n,r){return n<r}function tn(n,r,t){for(var i=-1,a=n.length;++i<a;){var f=n[i],e=r(f);if(e!=null&&(s===void 0?e===e&&!x(e):t(e,s)))var s=e,d=f}return d}function on(n){return n&&n.length?tn(n,B,rn):void 0}function an(n,r,t,i){if(!g(n))return n;r=I(r,n);for(var a=-1,f=r.length,e=f-1,s=n;s!=null&&++a<f;){var d=N(r[a]),u=t;if(d==="__proto__"||d==="constructor"||d==="prototype")return n;if(a!=e){var h=s[d];u=void 0,u===void 0&&(u=g(h)?h:S(r[a+1])?[]:{})}y(s,d,u),s=s[d]}return n}function vn(n,r,t){for(var i=-1,a=r.length,f={};++i<a;){var e=r[i],s=E(n,e);t(s,e)&&an(f,I(e,n),s)}return f}export{rn as a,tn as b,V as c,vn as d,on as e,fn as f,hn as g,mn as h,dn as i,U as j,un as l,gn as m,K as t};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{N as ln,O as an,P as Z,Q as O,R as V,S as un,T as y,V as tn,W as z,X as _,Y as rn,Z as o,$ as on,a0 as sn,a1 as fn}from"./mermaid.core-v0oo9NRr.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,D,S,v,R,W,a){var E=D-l,i=S-h,n=W-v,d=a-R,u=d*E-n*i;if(!(u*u<y))return u=(n*(h-R)-d*(l-v))/u,[l+u*E,h+u*i]}function J(l,h,D,S,v,R,W){var a=l-D,E=h-S,i=(W?R:-R)/z(a*a+E*E),n=i*E,d=-i*a,u=l+n,s=h+d,f=D+n,c=S+d,X=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,A=m*m+g*g,T=v-R,P=u*c-f*s,I=(g<0?-1:1)*z(on(0,T*T*A-P*P)),N=(P*g-m*I)/A,Q=(-P*m-g*I)/A,w=(P*g+m*I)/A,p=(-P*m+g*I)/A,x=N-X,e=Q-t,r=w-X,Y=p-t;return x*x+e*e>r*r+Y*Y&&(N=w,Q=p),{cx:N,cy:Q,x01:-n,y01:-d,x11:N*(v/T-1),y11:Q*(v/T-1)}}function hn(){var l=cn,h=yn,D=V(0),S=null,v=gn,R=dn,W=mn,a=null,E=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=R.apply(this,arguments)-un,X=rn(c-f),t=c>f;if(a||(a=n=E()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(X>tn-y)a.moveTo(s*Z(f),s*O(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*Z(c),u*O(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,A=f,T=c,P=X,I=X,N=W.apply(this,arguments)/2,Q=N>y&&(S?+S.apply(this,arguments):z(u*u+s*s)),w=_(rn(s-u)/2,+D.apply(this,arguments)),p=w,x=w,e,r;if(Q>y){var Y=sn(Q/u*O(N)),B=sn(Q/s*O(N));(P-=Y*2)>y?(Y*=t?1:-1,A+=Y,T-=Y):(P=0,A=T=(f+c)/2),(I-=B*2)>y?(B*=t?1:-1,m+=B,g-=B):(I=0,m=g=(f+c)/2)}var $=s*Z(m),j=s*O(m),C=u*Z(T),F=u*O(T);if(w>y){var G=s*Z(g),H=s*O(g),K=u*Z(A),L=u*O(A),q;if(X<an)if(q=pn($,j,K,L,G,H,C,F)){var M=$-q[0],U=j-q[1],k=G-q[0],b=H-q[1],nn=1/O(fn((M*k+U*b)/(z(M*M+U*U)*z(k*k+b*b)))/2),en=z(q[0]*q[0]+q[1]*q[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}I>y?x>y?(e=J(K,L,$,j,s,x,t),r=J(G,H,C,F,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo($,j),a.arc(0,0,s,m,g,!t)):a.moveTo($,j),!(u>y)||!(P>y)?a.lineTo(C,F):p>y?(e=J(C,F,G,H,u,-p,t),r=J($,j,K,L,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,A,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +R.apply(this,arguments))/2-an/2;return[Z(d)*n,O(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:V(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:V(+n),i):h},i.cornerRadius=function(n){return arguments.length?(D=typeof n=="function"?n:V(+n),i):D},i.padRadius=function(n){return arguments.length?(S=n==null?null:typeof n=="function"?n:V(+n),i):S},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:V(+n),i):v},i.endAngle=function(n){return arguments.length?(R=typeof n=="function"?n:V(+n),i):R},i.padAngle=function(n){return arguments.length?(W=typeof n=="function"?n:V(+n),i):W},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d}; import{N as ln,O as an,P as Z,Q as O,R as V,S as un,T as y,V as tn,W as z,X as _,Y as rn,Z as o,$ as on,a0 as sn,a1 as fn}from"./mermaid.core-CFUktQ8s.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,D,S,v,R,W,a){var E=D-l,i=S-h,n=W-v,d=a-R,u=d*E-n*i;if(!(u*u<y))return u=(n*(h-R)-d*(l-v))/u,[l+u*E,h+u*i]}function J(l,h,D,S,v,R,W){var a=l-D,E=h-S,i=(W?R:-R)/z(a*a+E*E),n=i*E,d=-i*a,u=l+n,s=h+d,f=D+n,c=S+d,X=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,A=m*m+g*g,T=v-R,P=u*c-f*s,I=(g<0?-1:1)*z(on(0,T*T*A-P*P)),N=(P*g-m*I)/A,Q=(-P*m-g*I)/A,w=(P*g+m*I)/A,p=(-P*m+g*I)/A,x=N-X,e=Q-t,r=w-X,Y=p-t;return x*x+e*e>r*r+Y*Y&&(N=w,Q=p),{cx:N,cy:Q,x01:-n,y01:-d,x11:N*(v/T-1),y11:Q*(v/T-1)}}function hn(){var l=cn,h=yn,D=V(0),S=null,v=gn,R=dn,W=mn,a=null,E=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=R.apply(this,arguments)-un,X=rn(c-f),t=c>f;if(a||(a=n=E()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(X>tn-y)a.moveTo(s*Z(f),s*O(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*Z(c),u*O(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,A=f,T=c,P=X,I=X,N=W.apply(this,arguments)/2,Q=N>y&&(S?+S.apply(this,arguments):z(u*u+s*s)),w=_(rn(s-u)/2,+D.apply(this,arguments)),p=w,x=w,e,r;if(Q>y){var Y=sn(Q/u*O(N)),B=sn(Q/s*O(N));(P-=Y*2)>y?(Y*=t?1:-1,A+=Y,T-=Y):(P=0,A=T=(f+c)/2),(I-=B*2)>y?(B*=t?1:-1,m+=B,g-=B):(I=0,m=g=(f+c)/2)}var $=s*Z(m),j=s*O(m),C=u*Z(T),F=u*O(T);if(w>y){var G=s*Z(g),H=s*O(g),K=u*Z(A),L=u*O(A),q;if(X<an)if(q=pn($,j,K,L,G,H,C,F)){var M=$-q[0],U=j-q[1],k=G-q[0],b=H-q[1],nn=1/O(fn((M*k+U*b)/(z(M*M+U*U)*z(k*k+b*b)))/2),en=z(q[0]*q[0]+q[1]*q[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}I>y?x>y?(e=J(K,L,$,j,s,x,t),r=J(G,H,C,F,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo($,j),a.arc(0,0,s,m,g,!t)):a.moveTo($,j),!(u>y)||!(P>y)?a.lineTo(C,F):p>y?(e=J(C,F,G,H,u,-p,t),r=J($,j,K,L,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,A,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +R.apply(this,arguments))/2-an/2;return[Z(d)*n,O(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:V(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:V(+n),i):h},i.cornerRadius=function(n){return arguments.length?(D=typeof n=="function"?n:V(+n),i):D},i.padRadius=function(n){return arguments.length?(S=n==null?null:typeof n=="function"?n:V(+n),i):S},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:V(+n),i):v},i.endAngle=function(n){return arguments.length?(R=typeof n=="function"?n:V(+n),i):R},i.padAngle=function(n){return arguments.length?(W=typeof n=="function"?n:V(+n),i):W},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
import{U as a,M as n}from"./mermaid.core-CFUktQ8s.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
-1
View File
@@ -1 +0,0 @@
import{U as a,M as n}from"./mermaid.core-v0oo9NRr.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{_ as i}from"./mermaid.core-v0oo9NRr.js";function t(c,e){c.accDescr&&e.setAccDescription?.(c.accDescr),c.accTitle&&e.setAccTitle?.(c.accTitle),c.title&&e.setDiagramTitle?.(c.title)}i(t,"populateCommonDb");export{t as p}; import{_ as i}from"./mermaid.core-CFUktQ8s.js";function t(c,e){c.accDescr&&e.setAccDescription?.(c.accDescr),c.accTitle&&e.setAccTitle?.(c.accTitle),c.title&&e.setDiagramTitle?.(c.title)}i(t,"populateCommonDb");export{t as p};
@@ -1 +1 @@
import{_ as a,d as o}from"./mermaid.core-v0oo9NRr.js";var d=a((t,e)=>{let n;return e==="sandbox"&&(n=o("#i"+t)),(e==="sandbox"?o(n.nodes()[0].contentDocument.body):o("body")).select(`[id="${t}"]`)},"getDiagramElement");export{d as g}; import{_ as a,d as o}from"./mermaid.core-CFUktQ8s.js";var d=a((t,e)=>{let n;return e==="sandbox"&&(n=o("#i"+t)),(e==="sandbox"?o(n.nodes()[0].contentDocument.body):o("body")).select(`[id="${t}"]`)},"getDiagramElement");export{d as g};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{_ as e}from"./mermaid.core-v0oo9NRr.js";var l=e(()=>` import{_ as e}from"./mermaid.core-CFUktQ8s.js";var l=e(()=>`
/* Font Awesome icon styling - consolidated */ /* Font Awesome icon styling - consolidated */
.label-icon { .label-icon {
display: inline-block; display: inline-block;
@@ -1 +1 @@
import{_ as a,e as w,l as x}from"./mermaid.core-v0oo9NRr.js";var d=a((e,t,i,r)=>{e.attr("class",i);const{width:o,height:h,x:n,y:c}=u(e,t);w(e,h,o,r);const s=l(n,c,o,h,t);e.attr("viewBox",s),x.debug(`viewBox configured: ${s} with padding: ${t}`)},"setupViewPortForSVG"),u=a((e,t)=>{const i=e.node()?.getBBox()||{width:0,height:0,x:0,y:0};return{width:i.width+t*2,height:i.height+t*2,x:i.x,y:i.y}},"calculateDimensionsWithPadding"),l=a((e,t,i,r,o)=>`${e-o} ${t-o} ${i} ${r}`,"createViewBox");export{d as s}; import{_ as a,e as w,l as x}from"./mermaid.core-CFUktQ8s.js";var d=a((e,t,i,r)=>{e.attr("class",i);const{width:o,height:h,x:n,y:c}=u(e,t);w(e,h,o,r);const s=l(n,c,o,h,t);e.attr("viewBox",s),x.debug(`viewBox configured: ${s} with padding: ${t}`)},"setupViewPortForSVG"),u=a((e,t)=>{const i=e.node()?.getBBox()||{width:0,height:0,x:0,y:0};return{width:i.width+t*2,height:i.height+t*2,x:i.x,y:i.y}},"calculateDimensionsWithPadding"),l=a((e,t,i,r,o)=>`${e-o} ${t-o} ${i} ${r}`,"createViewBox");export{d as s};
@@ -1 +1 @@
import{_ as s}from"./mermaid.core-v0oo9NRr.js";var t,e=(t=class{constructor(i){this.init=i,this.records=this.init()}reset(){this.records=this.init()}},s(t,"ImperativeState"),t);export{e as I}; import{_ as s}from"./mermaid.core-CFUktQ8s.js";var t,e=(t=class{constructor(i){this.init=i,this.records=this.init()}reset(){this.records=this.init()}},s(t,"ImperativeState"),t);export{e as I};
@@ -1 +1 @@
import{_ as n,L as o,j as l}from"./mermaid.core-v0oo9NRr.js";var x=n((s,t)=>{const e=s.append("rect");if(e.attr("x",t.x),e.attr("y",t.y),e.attr("fill",t.fill),e.attr("stroke",t.stroke),e.attr("width",t.width),e.attr("height",t.height),t.name&&e.attr("name",t.name),t.rx&&e.attr("rx",t.rx),t.ry&&e.attr("ry",t.ry),t.attrs!==void 0)for(const r in t.attrs)e.attr(r,t.attrs[r]);return t.class&&e.attr("class",t.class),e},"drawRect"),d=n((s,t)=>{const e={x:t.startx,y:t.starty,width:t.stopx-t.startx,height:t.stopy-t.starty,fill:t.fill,stroke:t.stroke,class:"rect"};x(s,e).lower()},"drawBackgroundRect"),g=n((s,t)=>{const e=t.text.replace(o," "),r=s.append("text");r.attr("x",t.x),r.attr("y",t.y),r.attr("class","legend"),r.style("text-anchor",t.anchor),t.class&&r.attr("class",t.class);const a=r.append("tspan");return a.attr("x",t.x+t.textMargin*2),a.text(e),r},"drawText"),h=n((s,t,e,r)=>{const a=s.append("image");a.attr("x",t),a.attr("y",e);const i=l.sanitizeUrl(r);a.attr("xlink:href",i)},"drawImage"),m=n((s,t,e,r)=>{const a=s.append("use");a.attr("x",t),a.attr("y",e);const i=l.sanitizeUrl(r);a.attr("xlink:href",`#${i}`)},"drawEmbeddedImage"),y=n(()=>({x:0,y:0,width:100,height:100,fill:"#EDF2AE",stroke:"#666",anchor:"start",rx:0,ry:0}),"getNoteRect"),p=n(()=>({x:0,y:0,width:100,height:100,"text-anchor":"start",style:"#666",textMargin:0,rx:0,ry:0,tspan:!0}),"getTextObj");export{d as a,p as b,m as c,x as d,h as e,g as f,y as g}; import{_ as n,L as o,j as l}from"./mermaid.core-CFUktQ8s.js";var x=n((s,t)=>{const e=s.append("rect");if(e.attr("x",t.x),e.attr("y",t.y),e.attr("fill",t.fill),e.attr("stroke",t.stroke),e.attr("width",t.width),e.attr("height",t.height),t.name&&e.attr("name",t.name),t.rx&&e.attr("rx",t.rx),t.ry&&e.attr("ry",t.ry),t.attrs!==void 0)for(const r in t.attrs)e.attr(r,t.attrs[r]);return t.class&&e.attr("class",t.class),e},"drawRect"),d=n((s,t)=>{const e={x:t.startx,y:t.starty,width:t.stopx-t.startx,height:t.stopy-t.starty,fill:t.fill,stroke:t.stroke,class:"rect"};x(s,e).lower()},"drawBackgroundRect"),g=n((s,t)=>{const e=t.text.replace(o," "),r=s.append("text");r.attr("x",t.x),r.attr("y",t.y),r.attr("class","legend"),r.style("text-anchor",t.anchor),t.class&&r.attr("class",t.class);const a=r.append("tspan");return a.attr("x",t.x+t.textMargin*2),a.text(e),r},"drawText"),h=n((s,t,e,r)=>{const a=s.append("image");a.attr("x",t),a.attr("y",e);const i=l.sanitizeUrl(r);a.attr("xlink:href",i)},"drawImage"),m=n((s,t,e,r)=>{const a=s.append("use");a.attr("x",t),a.attr("y",e);const i=l.sanitizeUrl(r);a.attr("xlink:href",`#${i}`)},"drawEmbeddedImage"),y=n(()=>({x:0,y:0,width:100,height:100,fill:"#EDF2AE",stroke:"#666",anchor:"start",rx:0,ry:0}),"getNoteRect"),p=n(()=>({x:0,y:0,width:100,height:100,"text-anchor":"start",style:"#666",textMargin:0,rx:0,ry:0,tspan:!0}),"getTextObj");export{d as a,p as b,m as c,x as d,h as e,g as f,y as g};
@@ -0,0 +1 @@
import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-Cq2XT3QN.js";import{_ as i}from"./mermaid.core-CFUktQ8s.js";import"./chunk-FMBD7UC4-BALYpKmy.js";import"./chunk-55IACEB6-fbeP3Dtn.js";import"./chunk-QN33PNHL-B0_3_NGo.js";import"./index-DNCGxUDM.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1 +0,0 @@
import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-eem5VR5l.js";import{_ as i}from"./mermaid.core-v0oo9NRr.js";import"./chunk-FMBD7UC4-HblipWIM.js";import"./chunk-55IACEB6-CtULfmDo.js";import"./chunk-QN33PNHL-DSThOC6-.js";import"./index-8cIrvc8q.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -0,0 +1 @@
import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-Cq2XT3QN.js";import{_ as i}from"./mermaid.core-CFUktQ8s.js";import"./chunk-FMBD7UC4-BALYpKmy.js";import"./chunk-55IACEB6-fbeP3Dtn.js";import"./chunk-QN33PNHL-B0_3_NGo.js";import"./index-DNCGxUDM.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1 +0,0 @@
import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-eem5VR5l.js";import{_ as i}from"./mermaid.core-v0oo9NRr.js";import"./chunk-FMBD7UC4-HblipWIM.js";import"./chunk-55IACEB6-CtULfmDo.js";import"./chunk-QN33PNHL-DSThOC6-.js";import"./index-8cIrvc8q.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
+1
View File
@@ -0,0 +1 @@
import{b as r}from"./_baseUniq-T1y-Xwdr.js";var e=4;function a(o){return r(o,e)}export{a as c};
-1
View File
@@ -1 +0,0 @@
import{b as r}from"./_baseUniq-DAOs4kUj.js";var e=4;function a(o){return r(o,e)}export{a as c};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{s as k,g as R,q as E,p as F,a as I,b as _,_ as l,H as D,y as G,D as f,E as P,F as C,l as z,K as H}from"./mermaid.core-v0oo9NRr.js";import{p as V}from"./chunk-4BX2VUAB-DWDvTYfd.js";import{p as W}from"./treemap-GDKQZRPO-DJjQsbt8.js";import"./index-8cIrvc8q.js";import"./_baseUniq-DAOs4kUj.js";import"./_basePickBy-CL4iQUG-.js";import"./clone-C1u3K6Fy.js";var h={showLegend:!0,ticks:5,max:null,min:0,graticule:"circle"},w={axes:[],curves:[],options:h},m=structuredClone(w),B=P.radar,j=l(()=>f({...B,...C().radar}),"getConfig"),b=l(()=>m.axes,"getAxes"),q=l(()=>m.curves,"getCurves"),K=l(()=>m.options,"getOptions"),N=l(a=>{m.axes=a.map(t=>({name:t.name,label:t.label??t.name}))},"setAxes"),U=l(a=>{m.curves=a.map(t=>({name:t.name,label:t.label??t.name,entries:X(t.entries)}))},"setCurves"),X=l(a=>{if(a[0].axis==null)return a.map(e=>e.value);const t=b();if(t.length===0)throw new Error("Axes must be populated before curves for reference entries");return t.map(e=>{const r=a.find(s=>s.axis?.$refText===e.name);if(r===void 0)throw new Error("Missing entry for axis "+e.label);return r.value})},"computeCurveEntries"),Y=l(a=>{const t=a.reduce((e,r)=>(e[r.name]=r,e),{});m.options={showLegend:t.showLegend?.value??h.showLegend,ticks:t.ticks?.value??h.ticks,max:t.max?.value??h.max,min:t.min?.value??h.min,graticule:t.graticule?.value??h.graticule}},"setOptions"),Z=l(()=>{G(),m=structuredClone(w)},"clear"),$={getAxes:b,getCurves:q,getOptions:K,setAxes:N,setCurves:U,setOptions:Y,getConfig:j,clear:Z,setAccTitle:_,getAccTitle:I,setDiagramTitle:F,getDiagramTitle:E,getAccDescription:R,setAccDescription:k},J=l(a=>{V(a,$);const{axes:t,curves:e,options:r}=a;$.setAxes(t),$.setCurves(e),$.setOptions(r)},"populate"),Q={parse:l(async a=>{const t=await W("radar",a);z.debug(t),J(t)},"parse")},tt=l((a,t,e,r)=>{const s=r.db,o=s.getAxes(),i=s.getCurves(),n=s.getOptions(),c=s.getConfig(),d=s.getDiagramTitle(),u=D(t),p=et(u,c),g=n.max??Math.max(...i.map(y=>Math.max(...y.entries))),x=n.min,v=Math.min(c.width,c.height)/2;at(p,o,v,n.ticks,n.graticule),rt(p,o,v,c),M(p,o,i,x,g,n.graticule,c),T(p,i,n.showLegend,c),p.append("text").attr("class","radarTitle").text(d).attr("x",0).attr("y",-c.height/2-c.marginTop)},"draw"),et=l((a,t)=>{const e=t.width+t.marginLeft+t.marginRight,r=t.height+t.marginTop+t.marginBottom,s={x:t.marginLeft+t.width/2,y:t.marginTop+t.height/2};return a.attr("viewbox",`0 0 ${e} ${r}`).attr("width",e).attr("height",r),a.append("g").attr("transform",`translate(${s.x}, ${s.y})`)},"drawFrame"),at=l((a,t,e,r,s)=>{if(s==="circle")for(let o=0;o<r;o++){const i=e*(o+1)/r;a.append("circle").attr("r",i).attr("class","radarGraticule")}else if(s==="polygon"){const o=t.length;for(let i=0;i<r;i++){const n=e*(i+1)/r,c=t.map((d,u)=>{const p=2*u*Math.PI/o-Math.PI/2,g=n*Math.cos(p),x=n*Math.sin(p);return`${g},${x}`}).join(" ");a.append("polygon").attr("points",c).attr("class","radarGraticule")}}},"drawGraticule"),rt=l((a,t,e,r)=>{const s=t.length;for(let o=0;o<s;o++){const i=t[o].label,n=2*o*Math.PI/s-Math.PI/2;a.append("line").attr("x1",0).attr("y1",0).attr("x2",e*r.axisScaleFactor*Math.cos(n)).attr("y2",e*r.axisScaleFactor*Math.sin(n)).attr("class","radarAxisLine"),a.append("text").text(i).attr("x",e*r.axisLabelFactor*Math.cos(n)).attr("y",e*r.axisLabelFactor*Math.sin(n)).attr("class","radarAxisLabel")}},"drawAxes");function M(a,t,e,r,s,o,i){const n=t.length,c=Math.min(i.width,i.height)/2;e.forEach((d,u)=>{if(d.entries.length!==n)return;const p=d.entries.map((g,x)=>{const v=2*Math.PI*x/n-Math.PI/2,y=A(g,r,s,c),O=y*Math.cos(v),S=y*Math.sin(v);return{x:O,y:S}});o==="circle"?a.append("path").attr("d",L(p,i.curveTension)).attr("class",`radarCurve-${u}`):o==="polygon"&&a.append("polygon").attr("points",p.map(g=>`${g.x},${g.y}`).join(" ")).attr("class",`radarCurve-${u}`)})}l(M,"drawCurves");function A(a,t,e,r){const s=Math.min(Math.max(a,t),e);return r*(s-t)/(e-t)}l(A,"relativeRadius");function L(a,t){const e=a.length;let r=`M${a[0].x},${a[0].y}`;for(let s=0;s<e;s++){const o=a[(s-1+e)%e],i=a[s],n=a[(s+1)%e],c=a[(s+2)%e],d={x:i.x+(n.x-o.x)*t,y:i.y+(n.y-o.y)*t},u={x:n.x-(c.x-i.x)*t,y:n.y-(c.y-i.y)*t};r+=` C${d.x},${d.y} ${u.x},${u.y} ${n.x},${n.y}`}return`${r} Z`}l(L,"closedRoundCurve");function T(a,t,e,r){if(!e)return;const s=(r.width/2+r.marginRight)*3/4,o=-(r.height/2+r.marginTop)*3/4,i=20;t.forEach((n,c)=>{const d=a.append("g").attr("transform",`translate(${s}, ${o+c*i})`);d.append("rect").attr("width",12).attr("height",12).attr("class",`radarLegendBox-${c}`),d.append("text").attr("x",16).attr("y",0).attr("class","radarLegendText").text(n.label)})}l(T,"drawLegend");var st={draw:tt},nt=l((a,t)=>{let e="";for(let r=0;r<a.THEME_COLOR_LIMIT;r++){const s=a[`cScale${r}`];e+=` import{s as k,g as R,q as E,p as F,a as I,b as _,_ as l,H as D,y as G,D as f,E as P,F as C,l as z,K as H}from"./mermaid.core-CFUktQ8s.js";import{p as V}from"./chunk-4BX2VUAB-DHPPu6Xd.js";import{p as W}from"./treemap-GDKQZRPO-DWvWdchV.js";import"./index-DNCGxUDM.js";import"./_baseUniq-T1y-Xwdr.js";import"./_basePickBy-8KTOl_Ov.js";import"./clone-4l7SFgdX.js";var h={showLegend:!0,ticks:5,max:null,min:0,graticule:"circle"},w={axes:[],curves:[],options:h},m=structuredClone(w),B=P.radar,j=l(()=>f({...B,...C().radar}),"getConfig"),b=l(()=>m.axes,"getAxes"),q=l(()=>m.curves,"getCurves"),K=l(()=>m.options,"getOptions"),N=l(a=>{m.axes=a.map(t=>({name:t.name,label:t.label??t.name}))},"setAxes"),U=l(a=>{m.curves=a.map(t=>({name:t.name,label:t.label??t.name,entries:X(t.entries)}))},"setCurves"),X=l(a=>{if(a[0].axis==null)return a.map(e=>e.value);const t=b();if(t.length===0)throw new Error("Axes must be populated before curves for reference entries");return t.map(e=>{const r=a.find(s=>s.axis?.$refText===e.name);if(r===void 0)throw new Error("Missing entry for axis "+e.label);return r.value})},"computeCurveEntries"),Y=l(a=>{const t=a.reduce((e,r)=>(e[r.name]=r,e),{});m.options={showLegend:t.showLegend?.value??h.showLegend,ticks:t.ticks?.value??h.ticks,max:t.max?.value??h.max,min:t.min?.value??h.min,graticule:t.graticule?.value??h.graticule}},"setOptions"),Z=l(()=>{G(),m=structuredClone(w)},"clear"),$={getAxes:b,getCurves:q,getOptions:K,setAxes:N,setCurves:U,setOptions:Y,getConfig:j,clear:Z,setAccTitle:_,getAccTitle:I,setDiagramTitle:F,getDiagramTitle:E,getAccDescription:R,setAccDescription:k},J=l(a=>{V(a,$);const{axes:t,curves:e,options:r}=a;$.setAxes(t),$.setCurves(e),$.setOptions(r)},"populate"),Q={parse:l(async a=>{const t=await W("radar",a);z.debug(t),J(t)},"parse")},tt=l((a,t,e,r)=>{const s=r.db,o=s.getAxes(),i=s.getCurves(),n=s.getOptions(),c=s.getConfig(),d=s.getDiagramTitle(),u=D(t),p=et(u,c),g=n.max??Math.max(...i.map(y=>Math.max(...y.entries))),x=n.min,v=Math.min(c.width,c.height)/2;at(p,o,v,n.ticks,n.graticule),rt(p,o,v,c),M(p,o,i,x,g,n.graticule,c),T(p,i,n.showLegend,c),p.append("text").attr("class","radarTitle").text(d).attr("x",0).attr("y",-c.height/2-c.marginTop)},"draw"),et=l((a,t)=>{const e=t.width+t.marginLeft+t.marginRight,r=t.height+t.marginTop+t.marginBottom,s={x:t.marginLeft+t.width/2,y:t.marginTop+t.height/2};return a.attr("viewbox",`0 0 ${e} ${r}`).attr("width",e).attr("height",r),a.append("g").attr("transform",`translate(${s.x}, ${s.y})`)},"drawFrame"),at=l((a,t,e,r,s)=>{if(s==="circle")for(let o=0;o<r;o++){const i=e*(o+1)/r;a.append("circle").attr("r",i).attr("class","radarGraticule")}else if(s==="polygon"){const o=t.length;for(let i=0;i<r;i++){const n=e*(i+1)/r,c=t.map((d,u)=>{const p=2*u*Math.PI/o-Math.PI/2,g=n*Math.cos(p),x=n*Math.sin(p);return`${g},${x}`}).join(" ");a.append("polygon").attr("points",c).attr("class","radarGraticule")}}},"drawGraticule"),rt=l((a,t,e,r)=>{const s=t.length;for(let o=0;o<s;o++){const i=t[o].label,n=2*o*Math.PI/s-Math.PI/2;a.append("line").attr("x1",0).attr("y1",0).attr("x2",e*r.axisScaleFactor*Math.cos(n)).attr("y2",e*r.axisScaleFactor*Math.sin(n)).attr("class","radarAxisLine"),a.append("text").text(i).attr("x",e*r.axisLabelFactor*Math.cos(n)).attr("y",e*r.axisLabelFactor*Math.sin(n)).attr("class","radarAxisLabel")}},"drawAxes");function M(a,t,e,r,s,o,i){const n=t.length,c=Math.min(i.width,i.height)/2;e.forEach((d,u)=>{if(d.entries.length!==n)return;const p=d.entries.map((g,x)=>{const v=2*Math.PI*x/n-Math.PI/2,y=A(g,r,s,c),O=y*Math.cos(v),S=y*Math.sin(v);return{x:O,y:S}});o==="circle"?a.append("path").attr("d",L(p,i.curveTension)).attr("class",`radarCurve-${u}`):o==="polygon"&&a.append("polygon").attr("points",p.map(g=>`${g.x},${g.y}`).join(" ")).attr("class",`radarCurve-${u}`)})}l(M,"drawCurves");function A(a,t,e,r){const s=Math.min(Math.max(a,t),e);return r*(s-t)/(e-t)}l(A,"relativeRadius");function L(a,t){const e=a.length;let r=`M${a[0].x},${a[0].y}`;for(let s=0;s<e;s++){const o=a[(s-1+e)%e],i=a[s],n=a[(s+1)%e],c=a[(s+2)%e],d={x:i.x+(n.x-o.x)*t,y:i.y+(n.y-o.y)*t},u={x:n.x-(c.x-i.x)*t,y:n.y-(c.y-i.y)*t};r+=` C${d.x},${d.y} ${u.x},${u.y} ${n.x},${n.y}`}return`${r} Z`}l(L,"closedRoundCurve");function T(a,t,e,r){if(!e)return;const s=(r.width/2+r.marginRight)*3/4,o=-(r.height/2+r.marginTop)*3/4,i=20;t.forEach((n,c)=>{const d=a.append("g").attr("transform",`translate(${s}, ${o+c*i})`);d.append("rect").attr("width",12).attr("height",12).attr("class",`radarLegendBox-${c}`),d.append("text").attr("x",16).attr("y",0).attr("class","radarLegendText").text(n.label)})}l(T,"drawLegend");var st={draw:tt},nt=l((a,t)=>{let e="";for(let r=0;r<a.THEME_COLOR_LIMIT;r++){const s=a[`cScale${r}`];e+=`
.radarCurve-${r} { .radarCurve-${r} {
color: ${s}; color: ${s};
fill: ${s}; fill: ${s};
@@ -1,4 +1,4 @@
import{_ as b,D as m,H as B,e as C,l as w,b as S,a as D,p as T,q as E,g as F,s as P,E as z,F as A,y as W}from"./mermaid.core-v0oo9NRr.js";import{p as _}from"./chunk-4BX2VUAB-DWDvTYfd.js";import{p as N}from"./treemap-GDKQZRPO-DJjQsbt8.js";import"./index-8cIrvc8q.js";import"./_baseUniq-DAOs4kUj.js";import"./_basePickBy-CL4iQUG-.js";import"./clone-C1u3K6Fy.js";var L=z.packet,u,v=(u=class{constructor(){this.packet=[],this.setAccTitle=S,this.getAccTitle=D,this.setDiagramTitle=T,this.getDiagramTitle=E,this.getAccDescription=F,this.setAccDescription=P}getConfig(){const t=m({...L,...A().packet});return t.showBits&&(t.paddingY+=10),t}getPacket(){return this.packet}pushWord(t){t.length>0&&this.packet.push(t)}clear(){W(),this.packet=[]}},b(u,"PacketDB"),u),M=1e4,Y=b((e,t)=>{_(e,t);let r=-1,o=[],n=1;const{bitsPerRow:l}=t.getConfig();for(let{start:a,end:i,bits:d,label:c}of e.blocks){if(a!==void 0&&i!==void 0&&i<a)throw new Error(`Packet block ${a} - ${i} is invalid. End must be greater than start.`);if(a??=r+1,a!==r+1)throw new Error(`Packet block ${a} - ${i??a} is not contiguous. It should start from ${r+1}.`);if(d===0)throw new Error(`Packet block ${a} is invalid. Cannot have a zero bit field.`);for(i??=a+(d??1)-1,d??=i-a+1,r=i,w.debug(`Packet block ${a} - ${r} with label ${c}`);o.length<=l+1&&t.getPacket().length<M;){const[p,s]=H({start:a,end:i,bits:d,label:c},n,l);if(o.push(p),p.end+1===n*l&&(t.pushWord(o),o=[],n++),!s)break;({start:a,end:i,bits:d,label:c}=s)}}t.pushWord(o)},"populate"),H=b((e,t,r)=>{if(e.start===void 0)throw new Error("start should have been set during first phase");if(e.end===void 0)throw new Error("end should have been set during first phase");if(e.start>e.end)throw new Error(`Block start ${e.start} is greater than block end ${e.end}.`);if(e.end+1<=t*r)return[e,void 0];const o=t*r-1,n=t*r;return[{start:e.start,end:o,label:e.label,bits:o-e.start},{start:n,end:e.end,label:e.label,bits:e.end-n}]},"getNextFittingBlock"),x={parser:{yy:void 0},parse:b(async e=>{const t=await N("packet",e),r=x.parser?.yy;if(!(r instanceof v))throw new Error("parser.parser?.yy was not a PacketDB. This is due to a bug within Mermaid, please report this issue at https://github.com/mermaid-js/mermaid/issues.");w.debug(t),Y(t,r)},"parse")},I=b((e,t,r,o)=>{const n=o.db,l=n.getConfig(),{rowHeight:a,paddingY:i,bitWidth:d,bitsPerRow:c}=l,p=n.getPacket(),s=n.getDiagramTitle(),h=a+i,g=h*(p.length+1)-(s?0:a),k=d*c+2,f=B(t);f.attr("viewbox",`0 0 ${k} ${g}`),C(f,g,k,l.useMaxWidth);for(const[y,$]of p.entries())O(f,$,y,l);f.append("text").text(s).attr("x",k/2).attr("y",g-h/2).attr("dominant-baseline","middle").attr("text-anchor","middle").attr("class","packetTitle")},"draw"),O=b((e,t,r,{rowHeight:o,paddingX:n,paddingY:l,bitWidth:a,bitsPerRow:i,showBits:d})=>{const c=e.append("g"),p=r*(o+l)+l;for(const s of t){const h=s.start%i*a+1,g=(s.end-s.start+1)*a-n;if(c.append("rect").attr("x",h).attr("y",p).attr("width",g).attr("height",o).attr("class","packetBlock"),c.append("text").attr("x",h+g/2).attr("y",p+o/2).attr("class","packetLabel").attr("dominant-baseline","middle").attr("text-anchor","middle").text(s.label),!d)continue;const k=s.end===s.start,f=p-2;c.append("text").attr("x",h+(k?g/2:0)).attr("y",f).attr("class","packetByte start").attr("dominant-baseline","auto").attr("text-anchor",k?"middle":"start").text(s.start),k||c.append("text").attr("x",h+g).attr("y",f).attr("class","packetByte end").attr("dominant-baseline","auto").attr("text-anchor","end").text(s.end)}},"drawWord"),j={draw:I},q={byteFontSize:"10px",startByteColor:"black",endByteColor:"black",labelColor:"black",labelFontSize:"12px",titleColor:"black",titleFontSize:"14px",blockStrokeColor:"black",blockStrokeWidth:"1",blockFillColor:"#efefef"},G=b(({packet:e}={})=>{const t=m(q,e);return` import{_ as b,D as m,H as B,e as C,l as w,b as S,a as D,p as T,q as E,g as F,s as P,E as z,F as A,y as W}from"./mermaid.core-CFUktQ8s.js";import{p as _}from"./chunk-4BX2VUAB-DHPPu6Xd.js";import{p as N}from"./treemap-GDKQZRPO-DWvWdchV.js";import"./index-DNCGxUDM.js";import"./_baseUniq-T1y-Xwdr.js";import"./_basePickBy-8KTOl_Ov.js";import"./clone-4l7SFgdX.js";var L=z.packet,u,v=(u=class{constructor(){this.packet=[],this.setAccTitle=S,this.getAccTitle=D,this.setDiagramTitle=T,this.getDiagramTitle=E,this.getAccDescription=F,this.setAccDescription=P}getConfig(){const t=m({...L,...A().packet});return t.showBits&&(t.paddingY+=10),t}getPacket(){return this.packet}pushWord(t){t.length>0&&this.packet.push(t)}clear(){W(),this.packet=[]}},b(u,"PacketDB"),u),M=1e4,Y=b((e,t)=>{_(e,t);let r=-1,o=[],n=1;const{bitsPerRow:l}=t.getConfig();for(let{start:a,end:i,bits:d,label:c}of e.blocks){if(a!==void 0&&i!==void 0&&i<a)throw new Error(`Packet block ${a} - ${i} is invalid. End must be greater than start.`);if(a??=r+1,a!==r+1)throw new Error(`Packet block ${a} - ${i??a} is not contiguous. It should start from ${r+1}.`);if(d===0)throw new Error(`Packet block ${a} is invalid. Cannot have a zero bit field.`);for(i??=a+(d??1)-1,d??=i-a+1,r=i,w.debug(`Packet block ${a} - ${r} with label ${c}`);o.length<=l+1&&t.getPacket().length<M;){const[p,s]=H({start:a,end:i,bits:d,label:c},n,l);if(o.push(p),p.end+1===n*l&&(t.pushWord(o),o=[],n++),!s)break;({start:a,end:i,bits:d,label:c}=s)}}t.pushWord(o)},"populate"),H=b((e,t,r)=>{if(e.start===void 0)throw new Error("start should have been set during first phase");if(e.end===void 0)throw new Error("end should have been set during first phase");if(e.start>e.end)throw new Error(`Block start ${e.start} is greater than block end ${e.end}.`);if(e.end+1<=t*r)return[e,void 0];const o=t*r-1,n=t*r;return[{start:e.start,end:o,label:e.label,bits:o-e.start},{start:n,end:e.end,label:e.label,bits:e.end-n}]},"getNextFittingBlock"),x={parser:{yy:void 0},parse:b(async e=>{const t=await N("packet",e),r=x.parser?.yy;if(!(r instanceof v))throw new Error("parser.parser?.yy was not a PacketDB. This is due to a bug within Mermaid, please report this issue at https://github.com/mermaid-js/mermaid/issues.");w.debug(t),Y(t,r)},"parse")},I=b((e,t,r,o)=>{const n=o.db,l=n.getConfig(),{rowHeight:a,paddingY:i,bitWidth:d,bitsPerRow:c}=l,p=n.getPacket(),s=n.getDiagramTitle(),h=a+i,g=h*(p.length+1)-(s?0:a),k=d*c+2,f=B(t);f.attr("viewbox",`0 0 ${k} ${g}`),C(f,g,k,l.useMaxWidth);for(const[y,$]of p.entries())O(f,$,y,l);f.append("text").text(s).attr("x",k/2).attr("y",g-h/2).attr("dominant-baseline","middle").attr("text-anchor","middle").attr("class","packetTitle")},"draw"),O=b((e,t,r,{rowHeight:o,paddingX:n,paddingY:l,bitWidth:a,bitsPerRow:i,showBits:d})=>{const c=e.append("g"),p=r*(o+l)+l;for(const s of t){const h=s.start%i*a+1,g=(s.end-s.start+1)*a-n;if(c.append("rect").attr("x",h).attr("y",p).attr("width",g).attr("height",o).attr("class","packetBlock"),c.append("text").attr("x",h+g/2).attr("y",p+o/2).attr("class","packetLabel").attr("dominant-baseline","middle").attr("text-anchor","middle").text(s.label),!d)continue;const k=s.end===s.start,f=p-2;c.append("text").attr("x",h+(k?g/2:0)).attr("y",f).attr("class","packetByte start").attr("dominant-baseline","auto").attr("text-anchor",k?"middle":"start").text(s.start),k||c.append("text").attr("x",h+g).attr("y",f).attr("class","packetByte end").attr("dominant-baseline","auto").attr("text-anchor","end").text(s.end)}},"drawWord"),j={draw:I},q={byteFontSize:"10px",startByteColor:"black",endByteColor:"black",labelColor:"black",labelFontSize:"12px",titleColor:"black",titleFontSize:"14px",blockStrokeColor:"black",blockStrokeWidth:"1",blockFillColor:"#efefef"},G=b(({packet:e}={})=>{const t=m(q,e);return`
.packetByte { .packetByte {
font-size: ${t.byteFontSize}; font-size: ${t.byteFontSize};
} }
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/FilmGrid-BM-3a1vS.js","assets/FilmGrid.vue_vue_type_script_setup_true_lang-Dj0SEfcW.js","assets/index-8cIrvc8q.js","assets/index-BJkaQ2c4.css","assets/useContentImages-7wLVntsF.js","assets/FilmDetail-0aNPT6Ze.js","assets/FilmDetail.vue_vue_type_script_setup_true_lang-BhKlPG3Y.js"])))=>i.map(i=>d[i]);
import{d as e,_ as r}from"./index-8cIrvc8q.js";const _={id:"film",name:"Film Renderer",contentType:"film",surfaces:["chat-preview","panel-preview","panel-play"],chatPreview:e(()=>r(()=>import("./FilmGrid-BM-3a1vS.js"),__vite__mapDeps([0,1,2,3,4]))),panelPreview:e(()=>r(()=>import("./FilmGrid-BM-3a1vS.js"),__vite__mapDeps([0,1,2,3,4]))),panelPlay:e(()=>r(()=>import("./FilmDetail-0aNPT6Ze.js"),__vite__mapDeps([5,6,2,3])))};export{_ as filmRenderer};
@@ -0,0 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/FilmGrid-Dz4qYSg3.js","assets/FilmGrid.vue_vue_type_script_setup_true_lang-CkIQ4bRp.js","assets/index-DNCGxUDM.js","assets/index-BJh-vGUe.css","assets/useContentImages-DdjyABL9.js","assets/FilmDetail-D9AyPH42.js","assets/FilmDetail.vue_vue_type_script_setup_true_lang-Cgf4f-Ng.js"])))=>i.map(i=>d[i]);
import{d as e,_ as r}from"./index-DNCGxUDM.js";const _={id:"film",name:"Film Renderer",contentType:"film",surfaces:["chat-preview","panel-preview","panel-play"],chatPreview:e(()=>r(()=>import("./FilmGrid-Dz4qYSg3.js"),__vite__mapDeps([0,1,2,3,4]))),panelPreview:e(()=>r(()=>import("./FilmGrid-Dz4qYSg3.js"),__vite__mapDeps([0,1,2,3,4]))),panelPlay:e(()=>r(()=>import("./FilmDetail-D9AyPH42.js"),__vite__mapDeps([5,6,2,3])))};export{_ as filmRenderer};
@@ -1,4 +1,4 @@
import{g as qt}from"./chunk-FMBD7UC4-HblipWIM.js";import{_ as m,n as Ot,l as $,c as b1,d as E1,o as Ht,r as Xt,u as it,b as Qt,s as Jt,p as Zt,a as $t,g as te,q as ee,k as se,t as ie,J as re,v as ae,x as st,y as ne,z as ue,A as oe}from"./mermaid.core-v0oo9NRr.js";import{g as le}from"./chunk-55IACEB6-CtULfmDo.js";import{s as ce}from"./chunk-QN33PNHL-DSThOC6-.js";import{c as he}from"./channel-Dg2Em7BA.js";import"./index-8cIrvc8q.js";var de="flowchart-",G1,pe=(G1=class{constructor(){this.vertexCounter=0,this.config=b1(),this.vertices=new Map,this.edges=[],this.classes=new Map,this.subGraphs=[],this.subGraphLookup=new Map,this.tooltips=new Map,this.subCount=0,this.firstGraphFlag=!0,this.secCount=-1,this.posCrossRef=[],this.funs=[],this.setAccTitle=Qt,this.setAccDescription=Jt,this.setDiagramTitle=Zt,this.getAccTitle=$t,this.getAccDescription=te,this.getDiagramTitle=ee,this.funs.push(this.setupToolTips.bind(this)),this.addVertex=this.addVertex.bind(this),this.firstGraph=this.firstGraph.bind(this),this.setDirection=this.setDirection.bind(this),this.addSubGraph=this.addSubGraph.bind(this),this.addLink=this.addLink.bind(this),this.setLink=this.setLink.bind(this),this.updateLink=this.updateLink.bind(this),this.addClass=this.addClass.bind(this),this.setClass=this.setClass.bind(this),this.destructLink=this.destructLink.bind(this),this.setClickEvent=this.setClickEvent.bind(this),this.setTooltip=this.setTooltip.bind(this),this.updateLinkInterpolate=this.updateLinkInterpolate.bind(this),this.setClickFun=this.setClickFun.bind(this),this.bindFunctions=this.bindFunctions.bind(this),this.lex={firstGraph:this.firstGraph.bind(this)},this.clear(),this.setGen("gen-2")}sanitizeText(i){return se.sanitizeText(i,this.config)}lookUpDomId(i){for(const r of this.vertices.values())if(r.id===i)return r.domId;return i}addVertex(i,r,a,n,l,g,c={},b){if(!i||i.trim().length===0)return;let u;if(b!==void 0){let k;b.includes(` import{g as qt}from"./chunk-FMBD7UC4-BALYpKmy.js";import{_ as m,n as Ot,l as $,c as b1,d as E1,o as Ht,r as Xt,u as it,b as Qt,s as Jt,p as Zt,a as $t,g as te,q as ee,k as se,t as ie,J as re,v as ae,x as st,y as ne,z as ue,A as oe}from"./mermaid.core-CFUktQ8s.js";import{g as le}from"./chunk-55IACEB6-fbeP3Dtn.js";import{s as ce}from"./chunk-QN33PNHL-B0_3_NGo.js";import{c as he}from"./channel-BXJ3-Z4Z.js";import"./index-DNCGxUDM.js";var de="flowchart-",G1,pe=(G1=class{constructor(){this.vertexCounter=0,this.config=b1(),this.vertices=new Map,this.edges=[],this.classes=new Map,this.subGraphs=[],this.subGraphLookup=new Map,this.tooltips=new Map,this.subCount=0,this.firstGraphFlag=!0,this.secCount=-1,this.posCrossRef=[],this.funs=[],this.setAccTitle=Qt,this.setAccDescription=Jt,this.setDiagramTitle=Zt,this.getAccTitle=$t,this.getAccDescription=te,this.getDiagramTitle=ee,this.funs.push(this.setupToolTips.bind(this)),this.addVertex=this.addVertex.bind(this),this.firstGraph=this.firstGraph.bind(this),this.setDirection=this.setDirection.bind(this),this.addSubGraph=this.addSubGraph.bind(this),this.addLink=this.addLink.bind(this),this.setLink=this.setLink.bind(this),this.updateLink=this.updateLink.bind(this),this.addClass=this.addClass.bind(this),this.setClass=this.setClass.bind(this),this.destructLink=this.destructLink.bind(this),this.setClickEvent=this.setClickEvent.bind(this),this.setTooltip=this.setTooltip.bind(this),this.updateLinkInterpolate=this.updateLinkInterpolate.bind(this),this.setClickFun=this.setClickFun.bind(this),this.bindFunctions=this.bindFunctions.bind(this),this.lex={firstGraph:this.firstGraph.bind(this)},this.clear(),this.setGen("gen-2")}sanitizeText(i){return se.sanitizeText(i,this.config)}lookUpDomId(i){for(const r of this.vertices.values())if(r.id===i)return r.domId;return i}addVertex(i,r,a,n,l,g,c={},b){if(!i||i.trim().length===0)return;let u;if(b!==void 0){let k;b.includes(`
`)?k=b+` `)?k=b+`
`:k=`{ `:k=`{
`+b+` `+b+`
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
import{_ as e,l as s,H as n,e as i,I as p}from"./mermaid.core-v0oo9NRr.js";import{p as g}from"./treemap-GDKQZRPO-DJjQsbt8.js";import"./index-8cIrvc8q.js";import"./_baseUniq-DAOs4kUj.js";import"./_basePickBy-CL4iQUG-.js";import"./clone-C1u3K6Fy.js";var v={parse:e(async r=>{const a=await g("info",r);s.debug(a)},"parse")},d={version:p.version+""},m=e(()=>d.version,"getVersion"),c={getVersion:m},l=e((r,a,o)=>{s.debug(`rendering info diagram import{_ as e,l as s,H as n,e as i,I as p}from"./mermaid.core-CFUktQ8s.js";import{p as g}from"./treemap-GDKQZRPO-DWvWdchV.js";import"./index-DNCGxUDM.js";import"./_baseUniq-T1y-Xwdr.js";import"./_basePickBy-8KTOl_Ov.js";import"./clone-4l7SFgdX.js";var v={parse:e(async r=>{const a=await g("info",r);s.debug(a)},"parse")},d={version:p.version+""},m=e(()=>d.version,"getVersion"),c={getVersion:m},l=e((r,a,o)=>{s.debug(`rendering info diagram
`+r);const t=n(a);i(t,100,400,!0),t.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size",32).style("text-anchor","middle").text(`v${o}`)},"draw"),f={draw:l},z={parser:v,db:c,renderer:f};export{z as diagram}; `+r);const t=n(a);i(t,100,400,!0),t.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size",32).style("text-anchor","middle").text(`v${o}`)},"draw"),f={draw:l},z={parser:v,db:c,renderer:f};export{z as diagram};
@@ -1,4 +1,4 @@
import{a as gt,g as lt,f as mt,d as xt}from"./chunk-TZMSLE5B-93PKdbpb.js";import{g as kt}from"./chunk-FMBD7UC4-HblipWIM.js";import{g as _t,s as vt,a as bt,b as wt,q as Tt,p as St,_ as n,c as R,d as X,e as $t,y as Mt}from"./mermaid.core-v0oo9NRr.js";import{d as et}from"./arc-UjuE1bPP.js";import"./index-8cIrvc8q.js";var U=(function(){var t=n(function(h,i,a,l){for(a=a||{},l=h.length;l--;a[h[l]]=i);return a},"o"),e=[6,8,10,11,12,14,16,17,18],s=[1,9],c=[1,10],r=[1,11],f=[1,12],u=[1,13],y=[1,14],g={trace:n(function(){},"trace"),yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,taskName:18,taskData:19,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",18:"taskName",19:"taskData"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,2]],performAction:n(function(i,a,l,d,p,o,b){var k=o.length-1;switch(p){case 1:return o[k-1];case 2:this.$=[];break;case 3:o[k-1].push(o[k]),this.$=o[k-1];break;case 4:case 5:this.$=o[k];break;case 6:case 7:this.$=[];break;case 8:d.setDiagramTitle(o[k].substr(6)),this.$=o[k].substr(6);break;case 9:this.$=o[k].trim(),d.setAccTitle(this.$);break;case 10:case 11:this.$=o[k].trim(),d.setAccDescription(this.$);break;case 12:d.addSection(o[k].substr(8)),this.$=o[k].substr(8);break;case 13:d.addTask(o[k-1],o[k]),this.$="task";break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:s,12:c,14:r,16:f,17:u,18:y},t(e,[2,7],{1:[2,1]}),t(e,[2,3]),{9:15,11:s,12:c,14:r,16:f,17:u,18:y},t(e,[2,5]),t(e,[2,6]),t(e,[2,8]),{13:[1,16]},{15:[1,17]},t(e,[2,11]),t(e,[2,12]),{19:[1,18]},t(e,[2,4]),t(e,[2,9]),t(e,[2,10]),t(e,[2,13])],defaultActions:{},parseError:n(function(i,a){if(a.recoverable)this.trace(i);else{var l=new Error(i);throw l.hash=a,l}},"parseError"),parse:n(function(i){var a=this,l=[0],d=[],p=[null],o=[],b=this.table,k="",C=0,K=0,dt=2,Q=1,yt=o.slice.call(arguments,1),_=Object.create(this.lexer),I={yy:{}};for(var O in this.yy)Object.prototype.hasOwnProperty.call(this.yy,O)&&(I.yy[O]=this.yy[O]);_.setInput(i,I.yy),I.yy.lexer=_,I.yy.parser=this,typeof _.yylloc>"u"&&(_.yylloc={});var Y=_.yylloc;o.push(Y);var ft=_.options&&_.options.ranges;typeof I.yy.parseError=="function"?this.parseError=I.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function pt(w){l.length=l.length-2*w,p.length=p.length-w,o.length=o.length-w}n(pt,"popStack");function D(){var w;return w=d.pop()||_.lex()||Q,typeof w!="number"&&(w instanceof Array&&(d=w,w=d.pop()),w=a.symbols_[w]||w),w}n(D,"lex");for(var v,A,T,q,F={},N,M,tt,z;;){if(A=l[l.length-1],this.defaultActions[A]?T=this.defaultActions[A]:((v===null||typeof v>"u")&&(v=D()),T=b[A]&&b[A][v]),typeof T>"u"||!T.length||!T[0]){var H="";z=[];for(N in b[A])this.terminals_[N]&&N>dt&&z.push("'"+this.terminals_[N]+"'");_.showPosition?H="Parse error on line "+(C+1)+`: import{a as gt,g as lt,f as mt,d as xt}from"./chunk-TZMSLE5B-Cajwq1yU.js";import{g as kt}from"./chunk-FMBD7UC4-BALYpKmy.js";import{g as _t,s as vt,a as bt,b as wt,q as Tt,p as St,_ as n,c as R,d as X,e as $t,y as Mt}from"./mermaid.core-CFUktQ8s.js";import{d as et}from"./arc-P9DEh39j.js";import"./index-DNCGxUDM.js";var U=(function(){var t=n(function(h,i,a,l){for(a=a||{},l=h.length;l--;a[h[l]]=i);return a},"o"),e=[6,8,10,11,12,14,16,17,18],s=[1,9],c=[1,10],r=[1,11],f=[1,12],u=[1,13],y=[1,14],g={trace:n(function(){},"trace"),yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,taskName:18,taskData:19,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",18:"taskName",19:"taskData"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,2]],performAction:n(function(i,a,l,d,p,o,b){var k=o.length-1;switch(p){case 1:return o[k-1];case 2:this.$=[];break;case 3:o[k-1].push(o[k]),this.$=o[k-1];break;case 4:case 5:this.$=o[k];break;case 6:case 7:this.$=[];break;case 8:d.setDiagramTitle(o[k].substr(6)),this.$=o[k].substr(6);break;case 9:this.$=o[k].trim(),d.setAccTitle(this.$);break;case 10:case 11:this.$=o[k].trim(),d.setAccDescription(this.$);break;case 12:d.addSection(o[k].substr(8)),this.$=o[k].substr(8);break;case 13:d.addTask(o[k-1],o[k]),this.$="task";break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:s,12:c,14:r,16:f,17:u,18:y},t(e,[2,7],{1:[2,1]}),t(e,[2,3]),{9:15,11:s,12:c,14:r,16:f,17:u,18:y},t(e,[2,5]),t(e,[2,6]),t(e,[2,8]),{13:[1,16]},{15:[1,17]},t(e,[2,11]),t(e,[2,12]),{19:[1,18]},t(e,[2,4]),t(e,[2,9]),t(e,[2,10]),t(e,[2,13])],defaultActions:{},parseError:n(function(i,a){if(a.recoverable)this.trace(i);else{var l=new Error(i);throw l.hash=a,l}},"parseError"),parse:n(function(i){var a=this,l=[0],d=[],p=[null],o=[],b=this.table,k="",C=0,K=0,dt=2,Q=1,yt=o.slice.call(arguments,1),_=Object.create(this.lexer),I={yy:{}};for(var O in this.yy)Object.prototype.hasOwnProperty.call(this.yy,O)&&(I.yy[O]=this.yy[O]);_.setInput(i,I.yy),I.yy.lexer=_,I.yy.parser=this,typeof _.yylloc>"u"&&(_.yylloc={});var Y=_.yylloc;o.push(Y);var ft=_.options&&_.options.ranges;typeof I.yy.parseError=="function"?this.parseError=I.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function pt(w){l.length=l.length-2*w,p.length=p.length-w,o.length=o.length-w}n(pt,"popStack");function D(){var w;return w=d.pop()||_.lex()||Q,typeof w!="number"&&(w instanceof Array&&(d=w,w=d.pop()),w=a.symbols_[w]||w),w}n(D,"lex");for(var v,A,T,q,F={},N,M,tt,z;;){if(A=l[l.length-1],this.defaultActions[A]?T=this.defaultActions[A]:((v===null||typeof v>"u")&&(v=D()),T=b[A]&&b[A][v]),typeof T>"u"||!T.length||!T[0]){var H="";z=[];for(N in b[A])this.terminals_[N]&&N>dt&&z.push("'"+this.terminals_[N]+"'");_.showPosition?H="Parse error on line "+(C+1)+`:
`+_.showPosition()+` `+_.showPosition()+`
Expecting `+z.join(", ")+", got '"+(this.terminals_[v]||v)+"'":H="Parse error on line "+(C+1)+": Unexpected "+(v==Q?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(H,{text:_.match,token:this.terminals_[v]||v,line:_.yylineno,loc:Y,expected:z})}if(T[0]instanceof Array&&T.length>1)throw new Error("Parse Error: multiple actions possible at state: "+A+", token: "+v);switch(T[0]){case 1:l.push(v),p.push(_.yytext),o.push(_.yylloc),l.push(T[1]),v=null,K=_.yyleng,k=_.yytext,C=_.yylineno,Y=_.yylloc;break;case 2:if(M=this.productions_[T[1]][1],F.$=p[p.length-M],F._$={first_line:o[o.length-(M||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(M||1)].first_column,last_column:o[o.length-1].last_column},ft&&(F._$.range=[o[o.length-(M||1)].range[0],o[o.length-1].range[1]]),q=this.performAction.apply(F,[k,K,C,I.yy,T[1],p,o].concat(yt)),typeof q<"u")return q;M&&(l=l.slice(0,-1*M*2),p=p.slice(0,-1*M),o=o.slice(0,-1*M)),l.push(this.productions_[T[1]][0]),p.push(F.$),o.push(F._$),tt=b[l[l.length-2]][l[l.length-1]],l.push(tt);break;case 3:return!0}}return!0},"parse")},m=(function(){var h={EOF:1,parseError:n(function(a,l){if(this.yy.parser)this.yy.parser.parseError(a,l);else throw new Error(a)},"parseError"),setInput:n(function(i,a){return this.yy=a||this.yy||{},this._input=i,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:n(function(){var i=this._input[0];this.yytext+=i,this.yyleng++,this.offset++,this.match+=i,this.matched+=i;var a=i.match(/(?:\r\n?|\n).*/g);return a?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),i},"input"),unput:n(function(i){var a=i.length,l=i.split(/(?:\r\n?|\n)/g);this._input=i+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-a),this.offset-=a;var d=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),l.length-1&&(this.yylineno-=l.length-1);var p=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:l?(l.length===d.length?this.yylloc.first_column:0)+d[d.length-l.length].length-l[0].length:this.yylloc.first_column-a},this.options.ranges&&(this.yylloc.range=[p[0],p[0]+this.yyleng-a]),this.yyleng=this.yytext.length,this},"unput"),more:n(function(){return this._more=!0,this},"more"),reject:n(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). Expecting `+z.join(", ")+", got '"+(this.terminals_[v]||v)+"'":H="Parse error on line "+(C+1)+": Unexpected "+(v==Q?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(H,{text:_.match,token:this.terminals_[v]||v,line:_.yylineno,loc:Y,expected:z})}if(T[0]instanceof Array&&T.length>1)throw new Error("Parse Error: multiple actions possible at state: "+A+", token: "+v);switch(T[0]){case 1:l.push(v),p.push(_.yytext),o.push(_.yylloc),l.push(T[1]),v=null,K=_.yyleng,k=_.yytext,C=_.yylineno,Y=_.yylloc;break;case 2:if(M=this.productions_[T[1]][1],F.$=p[p.length-M],F._$={first_line:o[o.length-(M||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(M||1)].first_column,last_column:o[o.length-1].last_column},ft&&(F._$.range=[o[o.length-(M||1)].range[0],o[o.length-1].range[1]]),q=this.performAction.apply(F,[k,K,C,I.yy,T[1],p,o].concat(yt)),typeof q<"u")return q;M&&(l=l.slice(0,-1*M*2),p=p.slice(0,-1*M),o=o.slice(0,-1*M)),l.push(this.productions_[T[1]][0]),p.push(F.$),o.push(F._$),tt=b[l[l.length-2]][l[l.length-1]],l.push(tt);break;case 3:return!0}}return!0},"parse")},m=(function(){var h={EOF:1,parseError:n(function(a,l){if(this.yy.parser)this.yy.parser.parseError(a,l);else throw new Error(a)},"parseError"),setInput:n(function(i,a){return this.yy=a||this.yy||{},this._input=i,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:n(function(){var i=this._input[0];this.yytext+=i,this.yyleng++,this.offset++,this.match+=i,this.matched+=i;var a=i.match(/(?:\r\n?|\n).*/g);return a?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),i},"input"),unput:n(function(i){var a=i.length,l=i.split(/(?:\r\n?|\n)/g);this._input=i+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-a),this.offset-=a;var d=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),l.length-1&&(this.yylineno-=l.length-1);var p=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:l?(l.length===d.length?this.yylloc.first_column:0)+d[d.length-l.length].length-l[0].length:this.yylloc.first_column-a},this.options.ranges&&(this.yylloc.range=[p[0],p[0]+this.yyleng-a]),this.yyleng=this.yytext.length,this},"unput"),more:n(function(){return this._more=!0,this},"more"),reject:n(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:n(function(i){this.unput(this.match.slice(i))},"less"),pastInput:n(function(){var i=this.matched.substr(0,this.matched.length-this.match.length);return(i.length>20?"...":"")+i.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:n(function(){var i=this.match;return i.length<20&&(i+=this._input.substr(0,20-i.length)),(i.substr(0,20)+(i.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:n(function(){var i=this.pastInput(),a=new Array(i.length+1).join("-");return i+this.upcomingInput()+` `+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:n(function(i){this.unput(this.match.slice(i))},"less"),pastInput:n(function(){var i=this.matched.substr(0,this.matched.length-this.match.length);return(i.length>20?"...":"")+i.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:n(function(){var i=this.match;return i.length<20&&(i+=this._input.substr(0,20-i.length)),(i.substr(0,20)+(i.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:n(function(){var i=this.pastInput(),a=new Array(i.length+1).join("-");return i+this.upcomingInput()+`
@@ -1,4 +1,4 @@
import{_ as o,l as te,c as U,H as fe,ah as ye,ai as be,aj as me,ac as Ee,E as K,i as F,t as _e,J as ke,ad as Se,ae as ce,af as le}from"./mermaid.core-v0oo9NRr.js";import{g as Ne}from"./chunk-FMBD7UC4-HblipWIM.js";import"./index-8cIrvc8q.js";var $=(function(){var e=o(function(O,i,n,r){for(n=n||{},r=O.length;r--;n[O[r]]=i);return n},"o"),u=[1,4],p=[1,13],s=[1,12],d=[1,15],_=[1,16],b=[1,20],l=[1,19],D=[6,7,8],I=[1,26],g=[1,24],w=[1,25],E=[6,7,11],G=[1,31],N=[6,7,11,24],V=[1,6,13,16,17,20,23],m=[1,35],A=[1,36],L=[1,6,7,11,13,16,17,20,23],H=[1,38],T={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,mindMap:4,spaceLines:5,SPACELINE:6,NL:7,KANBAN:8,document:9,stop:10,EOF:11,statement:12,SPACELIST:13,node:14,shapeData:15,ICON:16,CLASS:17,nodeWithId:18,nodeWithoutId:19,NODE_DSTART:20,NODE_DESCR:21,NODE_DEND:22,NODE_ID:23,SHAPE_DATA:24,$accept:0,$end:1},terminals_:{2:"error",6:"SPACELINE",7:"NL",8:"KANBAN",11:"EOF",13:"SPACELIST",16:"ICON",17:"CLASS",20:"NODE_DSTART",21:"NODE_DESCR",22:"NODE_DEND",23:"NODE_ID",24:"SHAPE_DATA"},productions_:[0,[3,1],[3,2],[5,1],[5,2],[5,2],[4,2],[4,3],[10,1],[10,1],[10,1],[10,2],[10,2],[9,3],[9,2],[12,3],[12,2],[12,2],[12,2],[12,1],[12,2],[12,1],[12,1],[12,1],[12,1],[14,1],[14,1],[19,3],[18,1],[18,4],[15,2],[15,1]],performAction:o(function(i,n,r,a,h,t,M){var c=t.length-1;switch(h){case 6:case 7:return a;case 8:a.getLogger().trace("Stop NL ");break;case 9:a.getLogger().trace("Stop EOF ");break;case 11:a.getLogger().trace("Stop NL2 ");break;case 12:a.getLogger().trace("Stop EOF2 ");break;case 15:a.getLogger().info("Node: ",t[c-1].id),a.addNode(t[c-2].length,t[c-1].id,t[c-1].descr,t[c-1].type,t[c]);break;case 16:a.getLogger().info("Node: ",t[c].id),a.addNode(t[c-1].length,t[c].id,t[c].descr,t[c].type);break;case 17:a.getLogger().trace("Icon: ",t[c]),a.decorateNode({icon:t[c]});break;case 18:case 23:a.decorateNode({class:t[c]});break;case 19:a.getLogger().trace("SPACELIST");break;case 20:a.getLogger().trace("Node: ",t[c-1].id),a.addNode(0,t[c-1].id,t[c-1].descr,t[c-1].type,t[c]);break;case 21:a.getLogger().trace("Node: ",t[c].id),a.addNode(0,t[c].id,t[c].descr,t[c].type);break;case 22:a.decorateNode({icon:t[c]});break;case 27:a.getLogger().trace("node found ..",t[c-2]),this.$={id:t[c-1],descr:t[c-1],type:a.getType(t[c-2],t[c])};break;case 28:this.$={id:t[c],descr:t[c],type:0};break;case 29:a.getLogger().trace("node found ..",t[c-3]),this.$={id:t[c-3],descr:t[c-1],type:a.getType(t[c-2],t[c])};break;case 30:this.$=t[c-1]+t[c];break;case 31:this.$=t[c];break}},"anonymous"),table:[{3:1,4:2,5:3,6:[1,5],8:u},{1:[3]},{1:[2,1]},{4:6,6:[1,7],7:[1,8],8:u},{6:p,7:[1,10],9:9,12:11,13:s,14:14,16:d,17:_,18:17,19:18,20:b,23:l},e(D,[2,3]),{1:[2,2]},e(D,[2,4]),e(D,[2,5]),{1:[2,6],6:p,12:21,13:s,14:14,16:d,17:_,18:17,19:18,20:b,23:l},{6:p,9:22,12:11,13:s,14:14,16:d,17:_,18:17,19:18,20:b,23:l},{6:I,7:g,10:23,11:w},e(E,[2,24],{18:17,19:18,14:27,16:[1,28],17:[1,29],20:b,23:l}),e(E,[2,19]),e(E,[2,21],{15:30,24:G}),e(E,[2,22]),e(E,[2,23]),e(N,[2,25]),e(N,[2,26]),e(N,[2,28],{20:[1,32]}),{21:[1,33]},{6:I,7:g,10:34,11:w},{1:[2,7],6:p,12:21,13:s,14:14,16:d,17:_,18:17,19:18,20:b,23:l},e(V,[2,14],{7:m,11:A}),e(L,[2,8]),e(L,[2,9]),e(L,[2,10]),e(E,[2,16],{15:37,24:G}),e(E,[2,17]),e(E,[2,18]),e(E,[2,20],{24:H}),e(N,[2,31]),{21:[1,39]},{22:[1,40]},e(V,[2,13],{7:m,11:A}),e(L,[2,11]),e(L,[2,12]),e(E,[2,15],{24:H}),e(N,[2,30]),{22:[1,41]},e(N,[2,27]),e(N,[2,29])],defaultActions:{2:[2,1],6:[2,2]},parseError:o(function(i,n){if(n.recoverable)this.trace(i);else{var r=new Error(i);throw r.hash=n,r}},"parseError"),parse:o(function(i){var n=this,r=[0],a=[],h=[null],t=[],M=this.table,c="",W=0,se=0,ue=2,re=1,ge=t.slice.call(arguments,1),y=Object.create(this.lexer),R={yy:{}};for(var J in this.yy)Object.prototype.hasOwnProperty.call(this.yy,J)&&(R.yy[J]=this.yy[J]);y.setInput(i,R.yy),R.yy.lexer=y,R.yy.parser=this,typeof y.yylloc>"u"&&(y.yylloc={});var q=y.yylloc;t.push(q);var de=y.options&&y.options.ranges;typeof R.yy.parseError=="function"?this.parseError=R.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function pe(S){r.length=r.length-2*S,h.length=h.length-S,t.length=t.length-S}o(pe,"popStack");function ae(){var S;return S=a.pop()||y.lex()||re,typeof S!="number"&&(S instanceof Array&&(a=S,S=a.pop()),S=n.symbols_[S]||S),S}o(ae,"lex");for(var k,P,x,Q,j={},z,C,oe,X;;){if(P=r[r.length-1],this.defaultActions[P]?x=this.defaultActions[P]:((k===null||typeof k>"u")&&(k=ae()),x=M[P]&&M[P][k]),typeof x>"u"||!x.length||!x[0]){var Z="";X=[];for(z in M[P])this.terminals_[z]&&z>ue&&X.push("'"+this.terminals_[z]+"'");y.showPosition?Z="Parse error on line "+(W+1)+`: import{_ as o,l as te,c as U,H as fe,ah as ye,ai as be,aj as me,ac as Ee,E as K,i as F,t as _e,J as ke,ad as Se,ae as ce,af as le}from"./mermaid.core-CFUktQ8s.js";import{g as Ne}from"./chunk-FMBD7UC4-BALYpKmy.js";import"./index-DNCGxUDM.js";var $=(function(){var e=o(function(O,i,n,r){for(n=n||{},r=O.length;r--;n[O[r]]=i);return n},"o"),u=[1,4],p=[1,13],s=[1,12],d=[1,15],_=[1,16],b=[1,20],l=[1,19],D=[6,7,8],I=[1,26],g=[1,24],w=[1,25],E=[6,7,11],G=[1,31],N=[6,7,11,24],V=[1,6,13,16,17,20,23],m=[1,35],A=[1,36],L=[1,6,7,11,13,16,17,20,23],H=[1,38],T={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,mindMap:4,spaceLines:5,SPACELINE:6,NL:7,KANBAN:8,document:9,stop:10,EOF:11,statement:12,SPACELIST:13,node:14,shapeData:15,ICON:16,CLASS:17,nodeWithId:18,nodeWithoutId:19,NODE_DSTART:20,NODE_DESCR:21,NODE_DEND:22,NODE_ID:23,SHAPE_DATA:24,$accept:0,$end:1},terminals_:{2:"error",6:"SPACELINE",7:"NL",8:"KANBAN",11:"EOF",13:"SPACELIST",16:"ICON",17:"CLASS",20:"NODE_DSTART",21:"NODE_DESCR",22:"NODE_DEND",23:"NODE_ID",24:"SHAPE_DATA"},productions_:[0,[3,1],[3,2],[5,1],[5,2],[5,2],[4,2],[4,3],[10,1],[10,1],[10,1],[10,2],[10,2],[9,3],[9,2],[12,3],[12,2],[12,2],[12,2],[12,1],[12,2],[12,1],[12,1],[12,1],[12,1],[14,1],[14,1],[19,3],[18,1],[18,4],[15,2],[15,1]],performAction:o(function(i,n,r,a,h,t,M){var c=t.length-1;switch(h){case 6:case 7:return a;case 8:a.getLogger().trace("Stop NL ");break;case 9:a.getLogger().trace("Stop EOF ");break;case 11:a.getLogger().trace("Stop NL2 ");break;case 12:a.getLogger().trace("Stop EOF2 ");break;case 15:a.getLogger().info("Node: ",t[c-1].id),a.addNode(t[c-2].length,t[c-1].id,t[c-1].descr,t[c-1].type,t[c]);break;case 16:a.getLogger().info("Node: ",t[c].id),a.addNode(t[c-1].length,t[c].id,t[c].descr,t[c].type);break;case 17:a.getLogger().trace("Icon: ",t[c]),a.decorateNode({icon:t[c]});break;case 18:case 23:a.decorateNode({class:t[c]});break;case 19:a.getLogger().trace("SPACELIST");break;case 20:a.getLogger().trace("Node: ",t[c-1].id),a.addNode(0,t[c-1].id,t[c-1].descr,t[c-1].type,t[c]);break;case 21:a.getLogger().trace("Node: ",t[c].id),a.addNode(0,t[c].id,t[c].descr,t[c].type);break;case 22:a.decorateNode({icon:t[c]});break;case 27:a.getLogger().trace("node found ..",t[c-2]),this.$={id:t[c-1],descr:t[c-1],type:a.getType(t[c-2],t[c])};break;case 28:this.$={id:t[c],descr:t[c],type:0};break;case 29:a.getLogger().trace("node found ..",t[c-3]),this.$={id:t[c-3],descr:t[c-1],type:a.getType(t[c-2],t[c])};break;case 30:this.$=t[c-1]+t[c];break;case 31:this.$=t[c];break}},"anonymous"),table:[{3:1,4:2,5:3,6:[1,5],8:u},{1:[3]},{1:[2,1]},{4:6,6:[1,7],7:[1,8],8:u},{6:p,7:[1,10],9:9,12:11,13:s,14:14,16:d,17:_,18:17,19:18,20:b,23:l},e(D,[2,3]),{1:[2,2]},e(D,[2,4]),e(D,[2,5]),{1:[2,6],6:p,12:21,13:s,14:14,16:d,17:_,18:17,19:18,20:b,23:l},{6:p,9:22,12:11,13:s,14:14,16:d,17:_,18:17,19:18,20:b,23:l},{6:I,7:g,10:23,11:w},e(E,[2,24],{18:17,19:18,14:27,16:[1,28],17:[1,29],20:b,23:l}),e(E,[2,19]),e(E,[2,21],{15:30,24:G}),e(E,[2,22]),e(E,[2,23]),e(N,[2,25]),e(N,[2,26]),e(N,[2,28],{20:[1,32]}),{21:[1,33]},{6:I,7:g,10:34,11:w},{1:[2,7],6:p,12:21,13:s,14:14,16:d,17:_,18:17,19:18,20:b,23:l},e(V,[2,14],{7:m,11:A}),e(L,[2,8]),e(L,[2,9]),e(L,[2,10]),e(E,[2,16],{15:37,24:G}),e(E,[2,17]),e(E,[2,18]),e(E,[2,20],{24:H}),e(N,[2,31]),{21:[1,39]},{22:[1,40]},e(V,[2,13],{7:m,11:A}),e(L,[2,11]),e(L,[2,12]),e(E,[2,15],{24:H}),e(N,[2,30]),{22:[1,41]},e(N,[2,27]),e(N,[2,29])],defaultActions:{2:[2,1],6:[2,2]},parseError:o(function(i,n){if(n.recoverable)this.trace(i);else{var r=new Error(i);throw r.hash=n,r}},"parseError"),parse:o(function(i){var n=this,r=[0],a=[],h=[null],t=[],M=this.table,c="",W=0,se=0,ue=2,re=1,ge=t.slice.call(arguments,1),y=Object.create(this.lexer),R={yy:{}};for(var J in this.yy)Object.prototype.hasOwnProperty.call(this.yy,J)&&(R.yy[J]=this.yy[J]);y.setInput(i,R.yy),R.yy.lexer=y,R.yy.parser=this,typeof y.yylloc>"u"&&(y.yylloc={});var q=y.yylloc;t.push(q);var de=y.options&&y.options.ranges;typeof R.yy.parseError=="function"?this.parseError=R.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function pe(S){r.length=r.length-2*S,h.length=h.length-S,t.length=t.length-S}o(pe,"popStack");function ae(){var S;return S=a.pop()||y.lex()||re,typeof S!="number"&&(S instanceof Array&&(a=S,S=a.pop()),S=n.symbols_[S]||S),S}o(ae,"lex");for(var k,P,x,Q,j={},z,C,oe,X;;){if(P=r[r.length-1],this.defaultActions[P]?x=this.defaultActions[P]:((k===null||typeof k>"u")&&(k=ae()),x=M[P]&&M[P][k]),typeof x>"u"||!x.length||!x[0]){var Z="";X=[];for(z in M[P])this.terminals_[z]&&z>ue&&X.push("'"+this.terminals_[z]+"'");y.showPosition?Z="Parse error on line "+(W+1)+`:
`+y.showPosition()+` `+y.showPosition()+`
Expecting `+X.join(", ")+", got '"+(this.terminals_[k]||k)+"'":Z="Parse error on line "+(W+1)+": Unexpected "+(k==re?"end of input":"'"+(this.terminals_[k]||k)+"'"),this.parseError(Z,{text:y.match,token:this.terminals_[k]||k,line:y.yylineno,loc:q,expected:X})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+P+", token: "+k);switch(x[0]){case 1:r.push(k),h.push(y.yytext),t.push(y.yylloc),r.push(x[1]),k=null,se=y.yyleng,c=y.yytext,W=y.yylineno,q=y.yylloc;break;case 2:if(C=this.productions_[x[1]][1],j.$=h[h.length-C],j._$={first_line:t[t.length-(C||1)].first_line,last_line:t[t.length-1].last_line,first_column:t[t.length-(C||1)].first_column,last_column:t[t.length-1].last_column},de&&(j._$.range=[t[t.length-(C||1)].range[0],t[t.length-1].range[1]]),Q=this.performAction.apply(j,[c,se,W,R.yy,x[1],h,t].concat(ge)),typeof Q<"u")return Q;C&&(r=r.slice(0,-1*C*2),h=h.slice(0,-1*C),t=t.slice(0,-1*C)),r.push(this.productions_[x[1]][0]),h.push(j.$),t.push(j._$),oe=M[r[r.length-2]][r[r.length-1]],r.push(oe);break;case 3:return!0}}return!0},"parse")},Y=(function(){var O={EOF:1,parseError:o(function(n,r){if(this.yy.parser)this.yy.parser.parseError(n,r);else throw new Error(n)},"parseError"),setInput:o(function(i,n){return this.yy=n||this.yy||{},this._input=i,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var i=this._input[0];this.yytext+=i,this.yyleng++,this.offset++,this.match+=i,this.matched+=i;var n=i.match(/(?:\r\n?|\n).*/g);return n?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),i},"input"),unput:o(function(i){var n=i.length,r=i.split(/(?:\r\n?|\n)/g);this._input=i+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-n),this.offset-=n;var a=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),r.length-1&&(this.yylineno-=r.length-1);var h=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:r?(r.length===a.length?this.yylloc.first_column:0)+a[a.length-r.length].length-r[0].length:this.yylloc.first_column-n},this.options.ranges&&(this.yylloc.range=[h[0],h[0]+this.yyleng-n]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). Expecting `+X.join(", ")+", got '"+(this.terminals_[k]||k)+"'":Z="Parse error on line "+(W+1)+": Unexpected "+(k==re?"end of input":"'"+(this.terminals_[k]||k)+"'"),this.parseError(Z,{text:y.match,token:this.terminals_[k]||k,line:y.yylineno,loc:q,expected:X})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+P+", token: "+k);switch(x[0]){case 1:r.push(k),h.push(y.yytext),t.push(y.yylloc),r.push(x[1]),k=null,se=y.yyleng,c=y.yytext,W=y.yylineno,q=y.yylloc;break;case 2:if(C=this.productions_[x[1]][1],j.$=h[h.length-C],j._$={first_line:t[t.length-(C||1)].first_line,last_line:t[t.length-1].last_line,first_column:t[t.length-(C||1)].first_column,last_column:t[t.length-1].last_column},de&&(j._$.range=[t[t.length-(C||1)].range[0],t[t.length-1].range[1]]),Q=this.performAction.apply(j,[c,se,W,R.yy,x[1],h,t].concat(ge)),typeof Q<"u")return Q;C&&(r=r.slice(0,-1*C*2),h=h.slice(0,-1*C),t=t.slice(0,-1*C)),r.push(this.productions_[x[1]][0]),h.push(j.$),t.push(j._$),oe=M[r[r.length-2]][r[r.length-1]],r.push(oe);break;case 3:return!0}}return!0},"parse")},Y=(function(){var O={EOF:1,parseError:o(function(n,r){if(this.yy.parser)this.yy.parser.parseError(n,r);else throw new Error(n)},"parseError"),setInput:o(function(i,n){return this.yy=n||this.yy||{},this._input=i,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var i=this._input[0];this.yytext+=i,this.yyleng++,this.offset++,this.match+=i,this.matched+=i;var n=i.match(/(?:\r\n?|\n).*/g);return n?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),i},"input"),unput:o(function(i){var n=i.length,r=i.split(/(?:\r\n?|\n)/g);this._input=i+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-n),this.offset-=n;var a=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),r.length-1&&(this.yylineno-=r.length-1);var h=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:r?(r.length===a.length?this.yylloc.first_column:0)+a[a.length-r.length].length-r[0].length:this.yylloc.first_column-n},this.options.ranges&&(this.yylloc.range=[h[0],h[0]+this.yyleng-n]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(i){this.unput(this.match.slice(i))},"less"),pastInput:o(function(){var i=this.matched.substr(0,this.matched.length-this.match.length);return(i.length>20?"...":"")+i.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var i=this.match;return i.length<20&&(i+=this._input.substr(0,20-i.length)),(i.substr(0,20)+(i.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var i=this.pastInput(),n=new Array(i.length+1).join("-");return i+this.upcomingInput()+` `+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(i){this.unput(this.match.slice(i))},"less"),pastInput:o(function(){var i=this.matched.substr(0,this.matched.length-this.match.length);return(i.length>20?"...":"")+i.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var i=this.match;return i.length<20&&(i+=this._input.substr(0,20-i.length)),(i.substr(0,20)+(i.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var i=this.pastInput(),n=new Array(i.length+1).join("-");return i+this.upcomingInput()+`
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{R as S,V as z,aG as j,g as q,s as H,a as Z,b as J,q as K,p as Q,_ as p,l as F,c as X,D as Y,H as ee,a4 as te,e as ae,y as re,E as ne}from"./mermaid.core-v0oo9NRr.js";import{p as ie}from"./chunk-4BX2VUAB-DWDvTYfd.js";import{p as se}from"./treemap-GDKQZRPO-DJjQsbt8.js";import{d as I}from"./arc-UjuE1bPP.js";import{o as le}from"./ordinal-Cboi1Yqb.js";import"./index-8cIrvc8q.js";import"./_baseUniq-DAOs4kUj.js";import"./_basePickBy-CL4iQUG-.js";import"./clone-C1u3K6Fy.js";import"./init-Gi6I4Gst.js";function oe(e,a){return a<e?-1:a>e?1:a>=e?0:NaN}function ce(e){return e}function ue(){var e=ce,a=oe,f=null,y=S(0),s=S(z),o=S(0);function l(t){var n,c=(t=j(t)).length,d,x,h=0,u=new Array(c),i=new Array(c),v=+y.apply(this,arguments),w=Math.min(z,Math.max(-z,s.apply(this,arguments)-v)),m,C=Math.min(Math.abs(w)/c,o.apply(this,arguments)),$=C*(w<0?-1:1),g;for(n=0;n<c;++n)(g=i[u[n]=n]=+e(t[n],n,t))>0&&(h+=g);for(a!=null?u.sort(function(A,D){return a(i[A],i[D])}):f!=null&&u.sort(function(A,D){return f(t[A],t[D])}),n=0,x=h?(w-c*$)/h:0;n<c;++n,v=m)d=u[n],g=i[d],m=v+(g>0?g*x:0)+$,i[d]={data:t[d],index:n,value:g,startAngle:v,endAngle:m,padAngle:C};return i}return l.value=function(t){return arguments.length?(e=typeof t=="function"?t:S(+t),l):e},l.sortValues=function(t){return arguments.length?(a=t,f=null,l):a},l.sort=function(t){return arguments.length?(f=t,a=null,l):f},l.startAngle=function(t){return arguments.length?(y=typeof t=="function"?t:S(+t),l):y},l.endAngle=function(t){return arguments.length?(s=typeof t=="function"?t:S(+t),l):s},l.padAngle=function(t){return arguments.length?(o=typeof t=="function"?t:S(+t),l):o},l}var pe=ne.pie,G={sections:new Map,showData:!1},T=G.sections,N=G.showData,de=structuredClone(pe),ge=p(()=>structuredClone(de),"getConfig"),fe=p(()=>{T=new Map,N=G.showData,re()},"clear"),me=p(({label:e,value:a})=>{if(a<0)throw new Error(`"${e}" has invalid value: ${a}. Negative values are not allowed in pie charts. All slice values must be >= 0.`);T.has(e)||(T.set(e,a),F.debug(`added new section: ${e}, with value: ${a}`))},"addSection"),he=p(()=>T,"getSections"),ve=p(e=>{N=e},"setShowData"),Se=p(()=>N,"getShowData"),L={getConfig:ge,clear:fe,setDiagramTitle:Q,getDiagramTitle:K,setAccTitle:J,getAccTitle:Z,setAccDescription:H,getAccDescription:q,addSection:me,getSections:he,setShowData:ve,getShowData:Se},ye=p((e,a)=>{ie(e,a),a.setShowData(e.showData),e.sections.map(a.addSection)},"populateDb"),xe={parse:p(async e=>{const a=await se("pie",e);F.debug(a),ye(a,L)},"parse")},we=p(e=>` import{R as S,V as z,aG as j,g as q,s as H,a as Z,b as J,q as K,p as Q,_ as p,l as F,c as X,D as Y,H as ee,a4 as te,e as ae,y as re,E as ne}from"./mermaid.core-CFUktQ8s.js";import{p as ie}from"./chunk-4BX2VUAB-DHPPu6Xd.js";import{p as se}from"./treemap-GDKQZRPO-DWvWdchV.js";import{d as I}from"./arc-P9DEh39j.js";import{o as le}from"./ordinal-Cboi1Yqb.js";import"./index-DNCGxUDM.js";import"./_baseUniq-T1y-Xwdr.js";import"./_basePickBy-8KTOl_Ov.js";import"./clone-4l7SFgdX.js";import"./init-Gi6I4Gst.js";function oe(e,a){return a<e?-1:a>e?1:a>=e?0:NaN}function ce(e){return e}function ue(){var e=ce,a=oe,f=null,y=S(0),s=S(z),o=S(0);function l(t){var n,c=(t=j(t)).length,d,x,h=0,u=new Array(c),i=new Array(c),v=+y.apply(this,arguments),w=Math.min(z,Math.max(-z,s.apply(this,arguments)-v)),m,C=Math.min(Math.abs(w)/c,o.apply(this,arguments)),$=C*(w<0?-1:1),g;for(n=0;n<c;++n)(g=i[u[n]=n]=+e(t[n],n,t))>0&&(h+=g);for(a!=null?u.sort(function(A,D){return a(i[A],i[D])}):f!=null&&u.sort(function(A,D){return f(t[A],t[D])}),n=0,x=h?(w-c*$)/h:0;n<c;++n,v=m)d=u[n],g=i[d],m=v+(g>0?g*x:0)+$,i[d]={data:t[d],index:n,value:g,startAngle:v,endAngle:m,padAngle:C};return i}return l.value=function(t){return arguments.length?(e=typeof t=="function"?t:S(+t),l):e},l.sortValues=function(t){return arguments.length?(a=t,f=null,l):a},l.sort=function(t){return arguments.length?(f=t,a=null,l):f},l.startAngle=function(t){return arguments.length?(y=typeof t=="function"?t:S(+t),l):y},l.endAngle=function(t){return arguments.length?(s=typeof t=="function"?t:S(+t),l):s},l.padAngle=function(t){return arguments.length?(o=typeof t=="function"?t:S(+t),l):o},l}var pe=ne.pie,G={sections:new Map,showData:!1},T=G.sections,N=G.showData,de=structuredClone(pe),ge=p(()=>structuredClone(de),"getConfig"),fe=p(()=>{T=new Map,N=G.showData,re()},"clear"),me=p(({label:e,value:a})=>{if(a<0)throw new Error(`"${e}" has invalid value: ${a}. Negative values are not allowed in pie charts. All slice values must be >= 0.`);T.has(e)||(T.set(e,a),F.debug(`added new section: ${e}, with value: ${a}`))},"addSection"),he=p(()=>T,"getSections"),ve=p(e=>{N=e},"setShowData"),Se=p(()=>N,"getShowData"),L={getConfig:ge,clear:fe,setDiagramTitle:Q,getDiagramTitle:K,setAccTitle:J,getAccTitle:Z,setAccDescription:H,getAccDescription:q,addSection:me,getSections:he,setShowData:ve,getShowData:Se},ye=p((e,a)=>{ie(e,a),a.setShowData(e.showData),e.sections.map(a.addSection)},"populateDb"),xe={parse:p(async e=>{const a=await se("pie",e);F.debug(a),ye(a,L)},"parse")},we=p(e=>`
.pieCircle{ .pieCircle{
stroke: ${e.pieStrokeColor}; stroke: ${e.pieStrokeColor};
stroke-width : ${e.pieStrokeWidth}; stroke-width : ${e.pieStrokeWidth};
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/SongGrid-C568K-c_.js","assets/SongGrid.vue_vue_type_script_setup_true_lang-IvAOIQYW.js","assets/index-8cIrvc8q.js","assets/index-BJkaQ2c4.css","assets/useContentImages-7wLVntsF.js","assets/SongDetail-BiVK4Yzb.js","assets/SongDetail.vue_vue_type_script_setup_true_lang-B3om3Z8v.js"])))=>i.map(i=>d[i]);
import{d as e,_ as r}from"./index-8cIrvc8q.js";const o={id:"song",name:"Song Renderer",contentType:"song",surfaces:["chat-preview","panel-preview","panel-play"],chatPreview:e(()=>r(()=>import("./SongGrid-C568K-c_.js"),__vite__mapDeps([0,1,2,3,4]))),panelPreview:e(()=>r(()=>import("./SongGrid-C568K-c_.js"),__vite__mapDeps([0,1,2,3,4]))),panelPlay:e(()=>r(()=>import("./SongDetail-BiVK4Yzb.js"),__vite__mapDeps([5,6,2,3])))};export{o as songRenderer};
@@ -0,0 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/SongGrid-BdJzZ_PP.js","assets/SongGrid.vue_vue_type_script_setup_true_lang-70SttNTZ.js","assets/index-DNCGxUDM.js","assets/index-BJh-vGUe.css","assets/useContentImages-DdjyABL9.js","assets/SongDetail-Cdt_oumO.js","assets/SongDetail.vue_vue_type_script_setup_true_lang-0mQhUBE8.js"])))=>i.map(i=>d[i]);
import{d as e,_ as r}from"./index-DNCGxUDM.js";const o={id:"song",name:"Song Renderer",contentType:"song",surfaces:["chat-preview","panel-preview","panel-play"],chatPreview:e(()=>r(()=>import("./SongGrid-BdJzZ_PP.js"),__vite__mapDeps([0,1,2,3,4]))),panelPreview:e(()=>r(()=>import("./SongGrid-BdJzZ_PP.js"),__vite__mapDeps([0,1,2,3,4]))),panelPlay:e(()=>r(()=>import("./SongDetail-Cdt_oumO.js"),__vite__mapDeps([5,6,2,3])))};export{o as songRenderer};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{s as e,b as r,a,S as s}from"./chunk-DI55MBZ5-C35X8NbN.js";import{_ as i}from"./mermaid.core-CFUktQ8s.js";import"./chunk-55IACEB6-fbeP3Dtn.js";import"./chunk-QN33PNHL-B0_3_NGo.js";import"./index-DNCGxUDM.js";var p={parser:a,get db(){return new s(2)},renderer:r,styles:e,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{p as diagram};
@@ -1 +0,0 @@
import{s as e,b as r,a,S as s}from"./chunk-DI55MBZ5-BzH7fNN2.js";import{_ as i}from"./mermaid.core-v0oo9NRr.js";import"./chunk-55IACEB6-CtULfmDo.js";import"./chunk-QN33PNHL-DSThOC6-.js";import"./index-8cIrvc8q.js";var p={parser:a,get db(){return new s(2)},renderer:r,styles:e,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{p as diagram};
@@ -1,4 +1,4 @@
import{_ as s,c as xt,l as E,d as j,ac as kt,ad as vt,ae as _t,af as bt,B as wt,ag as St,y as Et}from"./mermaid.core-v0oo9NRr.js";import{d as nt}from"./arc-UjuE1bPP.js";import"./index-8cIrvc8q.js";var Q=(function(){var n=s(function(x,r,a,c){for(a=a||{},c=x.length;c--;a[x[c]]=r);return a},"o"),t=[6,8,10,11,12,14,16,17,20,21],e=[1,9],l=[1,10],i=[1,11],d=[1,12],h=[1,13],f=[1,16],m=[1,17],p={trace:s(function(){},"trace"),yy:{},symbols_:{error:2,start:3,timeline:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,period_statement:18,event_statement:19,period:20,event:21,$accept:0,$end:1},terminals_:{2:"error",4:"timeline",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",20:"period",21:"event"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,1],[9,1],[18,1],[19,1]],performAction:s(function(r,a,c,u,y,o,w){var v=o.length-1;switch(y){case 1:return o[v-1];case 2:this.$=[];break;case 3:o[v-1].push(o[v]),this.$=o[v-1];break;case 4:case 5:this.$=o[v];break;case 6:case 7:this.$=[];break;case 8:u.getCommonDb().setDiagramTitle(o[v].substr(6)),this.$=o[v].substr(6);break;case 9:this.$=o[v].trim(),u.getCommonDb().setAccTitle(this.$);break;case 10:case 11:this.$=o[v].trim(),u.getCommonDb().setAccDescription(this.$);break;case 12:u.addSection(o[v].substr(8)),this.$=o[v].substr(8);break;case 15:u.addTask(o[v],0,""),this.$=o[v];break;case 16:u.addEvent(o[v].substr(2)),this.$=o[v];break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},n(t,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:e,12:l,14:i,16:d,17:h,18:14,19:15,20:f,21:m},n(t,[2,7],{1:[2,1]}),n(t,[2,3]),{9:18,11:e,12:l,14:i,16:d,17:h,18:14,19:15,20:f,21:m},n(t,[2,5]),n(t,[2,6]),n(t,[2,8]),{13:[1,19]},{15:[1,20]},n(t,[2,11]),n(t,[2,12]),n(t,[2,13]),n(t,[2,14]),n(t,[2,15]),n(t,[2,16]),n(t,[2,4]),n(t,[2,9]),n(t,[2,10])],defaultActions:{},parseError:s(function(r,a){if(a.recoverable)this.trace(r);else{var c=new Error(r);throw c.hash=a,c}},"parseError"),parse:s(function(r){var a=this,c=[0],u=[],y=[null],o=[],w=this.table,v="",N=0,P=0,V=2,U=1,H=o.slice.call(arguments,1),g=Object.create(this.lexer),b={yy:{}};for(var L in this.yy)Object.prototype.hasOwnProperty.call(this.yy,L)&&(b.yy[L]=this.yy[L]);g.setInput(r,b.yy),b.yy.lexer=g,b.yy.parser=this,typeof g.yylloc>"u"&&(g.yylloc={});var M=g.yylloc;o.push(M);var W=g.options&&g.options.ranges;typeof b.yy.parseError=="function"?this.parseError=b.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Z(T){c.length=c.length-2*T,y.length=y.length-T,o.length=o.length-T}s(Z,"popStack");function tt(){var T;return T=u.pop()||g.lex()||U,typeof T!="number"&&(T instanceof Array&&(u=T,T=u.pop()),T=a.symbols_[T]||T),T}s(tt,"lex");for(var S,A,I,J,R={},B,$,et,O;;){if(A=c[c.length-1],this.defaultActions[A]?I=this.defaultActions[A]:((S===null||typeof S>"u")&&(S=tt()),I=w[A]&&w[A][S]),typeof I>"u"||!I.length||!I[0]){var K="";O=[];for(B in w[A])this.terminals_[B]&&B>V&&O.push("'"+this.terminals_[B]+"'");g.showPosition?K="Parse error on line "+(N+1)+`: import{_ as s,c as xt,l as E,d as j,ac as kt,ad as vt,ae as _t,af as bt,B as wt,ag as St,y as Et}from"./mermaid.core-CFUktQ8s.js";import{d as nt}from"./arc-P9DEh39j.js";import"./index-DNCGxUDM.js";var Q=(function(){var n=s(function(x,r,a,c){for(a=a||{},c=x.length;c--;a[x[c]]=r);return a},"o"),t=[6,8,10,11,12,14,16,17,20,21],e=[1,9],l=[1,10],i=[1,11],d=[1,12],h=[1,13],f=[1,16],m=[1,17],p={trace:s(function(){},"trace"),yy:{},symbols_:{error:2,start:3,timeline:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,period_statement:18,event_statement:19,period:20,event:21,$accept:0,$end:1},terminals_:{2:"error",4:"timeline",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",20:"period",21:"event"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,1],[9,1],[18,1],[19,1]],performAction:s(function(r,a,c,u,y,o,w){var v=o.length-1;switch(y){case 1:return o[v-1];case 2:this.$=[];break;case 3:o[v-1].push(o[v]),this.$=o[v-1];break;case 4:case 5:this.$=o[v];break;case 6:case 7:this.$=[];break;case 8:u.getCommonDb().setDiagramTitle(o[v].substr(6)),this.$=o[v].substr(6);break;case 9:this.$=o[v].trim(),u.getCommonDb().setAccTitle(this.$);break;case 10:case 11:this.$=o[v].trim(),u.getCommonDb().setAccDescription(this.$);break;case 12:u.addSection(o[v].substr(8)),this.$=o[v].substr(8);break;case 15:u.addTask(o[v],0,""),this.$=o[v];break;case 16:u.addEvent(o[v].substr(2)),this.$=o[v];break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},n(t,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:e,12:l,14:i,16:d,17:h,18:14,19:15,20:f,21:m},n(t,[2,7],{1:[2,1]}),n(t,[2,3]),{9:18,11:e,12:l,14:i,16:d,17:h,18:14,19:15,20:f,21:m},n(t,[2,5]),n(t,[2,6]),n(t,[2,8]),{13:[1,19]},{15:[1,20]},n(t,[2,11]),n(t,[2,12]),n(t,[2,13]),n(t,[2,14]),n(t,[2,15]),n(t,[2,16]),n(t,[2,4]),n(t,[2,9]),n(t,[2,10])],defaultActions:{},parseError:s(function(r,a){if(a.recoverable)this.trace(r);else{var c=new Error(r);throw c.hash=a,c}},"parseError"),parse:s(function(r){var a=this,c=[0],u=[],y=[null],o=[],w=this.table,v="",N=0,P=0,V=2,U=1,H=o.slice.call(arguments,1),g=Object.create(this.lexer),b={yy:{}};for(var L in this.yy)Object.prototype.hasOwnProperty.call(this.yy,L)&&(b.yy[L]=this.yy[L]);g.setInput(r,b.yy),b.yy.lexer=g,b.yy.parser=this,typeof g.yylloc>"u"&&(g.yylloc={});var M=g.yylloc;o.push(M);var W=g.options&&g.options.ranges;typeof b.yy.parseError=="function"?this.parseError=b.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Z(T){c.length=c.length-2*T,y.length=y.length-T,o.length=o.length-T}s(Z,"popStack");function tt(){var T;return T=u.pop()||g.lex()||U,typeof T!="number"&&(T instanceof Array&&(u=T,T=u.pop()),T=a.symbols_[T]||T),T}s(tt,"lex");for(var S,A,I,J,R={},B,$,et,O;;){if(A=c[c.length-1],this.defaultActions[A]?I=this.defaultActions[A]:((S===null||typeof S>"u")&&(S=tt()),I=w[A]&&w[A][S]),typeof I>"u"||!I.length||!I[0]){var K="";O=[];for(B in w[A])this.terminals_[B]&&B>V&&O.push("'"+this.terminals_[B]+"'");g.showPosition?K="Parse error on line "+(N+1)+`:
`+g.showPosition()+` `+g.showPosition()+`
Expecting `+O.join(", ")+", got '"+(this.terminals_[S]||S)+"'":K="Parse error on line "+(N+1)+": Unexpected "+(S==U?"end of input":"'"+(this.terminals_[S]||S)+"'"),this.parseError(K,{text:g.match,token:this.terminals_[S]||S,line:g.yylineno,loc:M,expected:O})}if(I[0]instanceof Array&&I.length>1)throw new Error("Parse Error: multiple actions possible at state: "+A+", token: "+S);switch(I[0]){case 1:c.push(S),y.push(g.yytext),o.push(g.yylloc),c.push(I[1]),S=null,P=g.yyleng,v=g.yytext,N=g.yylineno,M=g.yylloc;break;case 2:if($=this.productions_[I[1]][1],R.$=y[y.length-$],R._$={first_line:o[o.length-($||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-($||1)].first_column,last_column:o[o.length-1].last_column},W&&(R._$.range=[o[o.length-($||1)].range[0],o[o.length-1].range[1]]),J=this.performAction.apply(R,[v,P,N,b.yy,I[1],y,o].concat(H)),typeof J<"u")return J;$&&(c=c.slice(0,-1*$*2),y=y.slice(0,-1*$),o=o.slice(0,-1*$)),c.push(this.productions_[I[1]][0]),y.push(R.$),o.push(R._$),et=w[c[c.length-2]][c[c.length-1]],c.push(et);break;case 3:return!0}}return!0},"parse")},k=(function(){var x={EOF:1,parseError:s(function(a,c){if(this.yy.parser)this.yy.parser.parseError(a,c);else throw new Error(a)},"parseError"),setInput:s(function(r,a){return this.yy=a||this.yy||{},this._input=r,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:s(function(){var r=this._input[0];this.yytext+=r,this.yyleng++,this.offset++,this.match+=r,this.matched+=r;var a=r.match(/(?:\r\n?|\n).*/g);return a?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),r},"input"),unput:s(function(r){var a=r.length,c=r.split(/(?:\r\n?|\n)/g);this._input=r+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-a),this.offset-=a;var u=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),c.length-1&&(this.yylineno-=c.length-1);var y=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:c?(c.length===u.length?this.yylloc.first_column:0)+u[u.length-c.length].length-c[0].length:this.yylloc.first_column-a},this.options.ranges&&(this.yylloc.range=[y[0],y[0]+this.yyleng-a]),this.yyleng=this.yytext.length,this},"unput"),more:s(function(){return this._more=!0,this},"more"),reject:s(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). Expecting `+O.join(", ")+", got '"+(this.terminals_[S]||S)+"'":K="Parse error on line "+(N+1)+": Unexpected "+(S==U?"end of input":"'"+(this.terminals_[S]||S)+"'"),this.parseError(K,{text:g.match,token:this.terminals_[S]||S,line:g.yylineno,loc:M,expected:O})}if(I[0]instanceof Array&&I.length>1)throw new Error("Parse Error: multiple actions possible at state: "+A+", token: "+S);switch(I[0]){case 1:c.push(S),y.push(g.yytext),o.push(g.yylloc),c.push(I[1]),S=null,P=g.yyleng,v=g.yytext,N=g.yylineno,M=g.yylloc;break;case 2:if($=this.productions_[I[1]][1],R.$=y[y.length-$],R._$={first_line:o[o.length-($||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-($||1)].first_column,last_column:o[o.length-1].last_column},W&&(R._$.range=[o[o.length-($||1)].range[0],o[o.length-1].range[1]]),J=this.performAction.apply(R,[v,P,N,b.yy,I[1],y,o].concat(H)),typeof J<"u")return J;$&&(c=c.slice(0,-1*$*2),y=y.slice(0,-1*$),o=o.slice(0,-1*$)),c.push(this.productions_[I[1]][0]),y.push(R.$),o.push(R._$),et=w[c[c.length-2]][c[c.length-1]],c.push(et);break;case 3:return!0}}return!0},"parse")},k=(function(){var x={EOF:1,parseError:s(function(a,c){if(this.yy.parser)this.yy.parser.parseError(a,c);else throw new Error(a)},"parseError"),setInput:s(function(r,a){return this.yy=a||this.yy||{},this._input=r,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:s(function(){var r=this._input[0];this.yytext+=r,this.yyleng++,this.offset++,this.match+=r,this.matched+=r;var a=r.match(/(?:\r\n?|\n).*/g);return a?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),r},"input"),unput:s(function(r){var a=r.length,c=r.split(/(?:\r\n?|\n)/g);this._input=r+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-a),this.offset-=a;var u=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),c.length-1&&(this.yylineno-=c.length-1);var y=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:c?(c.length===u.length?this.yylloc.first_column:0)+u[u.length-c.length].length-c[0].length:this.yylloc.first_column-a},this.options.ranges&&(this.yylloc.range=[y[0],y[0]+this.yyleng-a]),this.yyleng=this.yytext.length,this},"unput"),more:s(function(){return this._more=!0,this},"more"),reject:s(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:s(function(r){this.unput(this.match.slice(r))},"less"),pastInput:s(function(){var r=this.matched.substr(0,this.matched.length-this.match.length);return(r.length>20?"...":"")+r.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:s(function(){var r=this.match;return r.length<20&&(r+=this._input.substr(0,20-r.length)),(r.substr(0,20)+(r.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:s(function(){var r=this.pastInput(),a=new Array(r.length+1).join("-");return r+this.upcomingInput()+` `+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:s(function(r){this.unput(this.match.slice(r))},"less"),pastInput:s(function(){var r=this.matched.substr(0,this.matched.length-this.match.length);return(r.length>20?"...":"")+r.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:s(function(){var r=this.match;return r.length<20&&(r+=this._input.substr(0,20-r.length)),(r.substr(0,20)+(r.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:s(function(){var r=this.pastInput(),a=new Array(r.length+1).join("-");return r+this.upcomingInput()+`
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{A as v,r as g,B as w,C as I}from"./index-8cIrvc8q.js";function k(t){const a=g(new Set),r=w(new Map);function i(e){a.value=new Set([...a.value,e])}function f(e){const n=t.id(e);return a.value.has(n)?null:t.existingUrl(e)||r.get(n)||null}function l(e){return t.fallback(e)}function d(e){i(t.id(e))}function o(e){const n=t.id(e);return!f(e)&&!a.value.has(n)}function h(e){for(const n of e){const c=t.id(n);t.existingUrl(n)||r.has(c)||a.value.has(c)||t.fetch(n).then(s=>{s?r.set(c,s):i(c)}).catch(()=>{i(c)})}}const u=t.items,m=I(u)?()=>u.value:u;return v(m,e=>h(e),{immediate:!0}),{coverSrc:f,fallbackSrc:l,onError:d,isLoading:o}}export{k as u}; import{A as v,r as g,B as w,C as I}from"./index-DNCGxUDM.js";function k(t){const a=g(new Set),r=w(new Map);function i(e){a.value=new Set([...a.value,e])}function f(e){const n=t.id(e);return a.value.has(n)?null:t.existingUrl(e)||r.get(n)||null}function l(e){return t.fallback(e)}function d(e){i(t.id(e))}function o(e){const n=t.id(e);return!f(e)&&!a.value.has(n)}function h(e){for(const n of e){const c=t.id(n);t.existingUrl(n)||r.has(c)||a.value.has(c)||t.fetch(n).then(s=>{s?r.set(c,s):i(c)}).catch(()=>{i(c)})}}const u=t.items,m=I(u)?()=>u.value:u;return v(m,e=>h(e),{immediate:!0}),{coverSrc:f,fallbackSrc:l,onError:d,isLoading:o}}export{k as u};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -22,8 +22,8 @@
<link rel="icon" href="/aiui/favicon.svg" type="image/svg+xml" /> <link rel="icon" href="/aiui/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/aiui/apple-touch-icon-180x180.png" /> <link rel="apple-touch-icon" href="/aiui/apple-touch-icon-180x180.png" />
<title>AIUI</title> <title>AIUI</title>
<script type="module" crossorigin src="/aiui/assets/index-8cIrvc8q.js"></script> <script type="module" crossorigin src="/aiui/assets/index-DNCGxUDM.js"></script>
<link rel="stylesheet" crossorigin href="/aiui/assets/index-BJkaQ2c4.css"> <link rel="stylesheet" crossorigin href="/aiui/assets/index-BJh-vGUe.css">
<link rel="manifest" href="/aiui/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/aiui/registerSW.js"></script></head> <link rel="manifest" href="/aiui/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/aiui/registerSW.js"></script></head>
<body class="antialiased h-full overflow-hidden fixed w-full"> <body class="antialiased h-full overflow-hidden fixed w-full">
<div id="app"></div> <div id="app"></div>
+1 -1
View File
File diff suppressed because one or more lines are too long
+22
View File
@@ -159,6 +159,14 @@ the top-level origin (OAuth redirect flows, WebAuthn), or sets
in the real embedded app session, **not** a plain browser tab: tabs don't in the real embedded app session, **not** a plain browser tab: tabs don't
enforce framing headers, so a tab proves nothing about the iframe. enforce framing headers, so a tab proves nothing about the iframe.
**The dashboard also self-heals**: if a running app's frame fails to load,
the session offers "open in tab" and *remembers the app as a tab app* — every
later launch opens a tab directly, with the tab-launch icon on its button.
The memory clears itself when the app later embeds successfully, and expires
weekly so fixes get re-probed. This safety net is not a licence to skip the
manifest flag: declaring `open_in_new_tab: true` up front spares your users
the one dead-pane encounter the detector needs.
(Historical note: before the gate handled this, embeddable-but-blocking apps (Historical note: before the gate handled this, embeddable-but-blocking apps
each carried a hand-built nginx strip proxy — gitea's port-3000 proxy is the each carried a hand-built nginx strip proxy — gitea's port-3000 proxy is the
surviving example. Do not copy that pattern for new apps.) surviving example. Do not copy that pattern for new apps.)
@@ -523,6 +531,20 @@ The broader app update policy for `1.8-alpha` is still being finalized. Until th
## App Icon ## App Icon
- **The tile plate (dark rounded background, border, sheen) is applied by
the system** — every surface renders your icon with the house
`archy-app-icon` treatment automatically. What the system deliberately
does NOT add is an inner margin (icons that already carry whitespace
would double-margin), so your file needs it baked in: **square canvas,
~12% margin per side**. Edge-to-edge marks look wrong next to every
other tile. Don't hand-tune it: run your mark through the normalizer
and commit its output —
```bash
scripts/normalize-app-icon.py your-mark.svg apps-icon-output.svg
```
It wraps any SVG (full-bleed upstream logos included) onto the house
canvas with the standard margin, preserving the original artwork
untouched inside.
- Provide a URL to your app icon (PNG, WebP, or SVG) - Provide a URL to your app icon (PNG, WebP, or SVG)
- Recommended size: 256x256 pixels - Recommended size: 256x256 pixels
- Square aspect ratio - Square aspect ratio
@@ -3244,10 +3244,22 @@ esac
if [ -t 0 ] && [ -z "$ARCHIPELAGO_WELCOMED" ]; then if [ -t 0 ] && [ -z "$ARCHIPELAGO_WELCOMED" ]; then
export ARCHIPELAGO_WELCOMED=1 export ARCHIPELAGO_WELCOMED=1
# Wait for network (DHCP may not be ready yet on first boot) # Wait for network (DHCP may not be ready yet on first boot).
# The address shown must be one a LAN user can actually reach: the
# default route's source address. `hostname -I` lists addresses in
# interface order, so a node with WireGuard up advertised 10.44.0.1 —
# its own tunnel address, present on EVERY node — as its "web ui",
# which is unreachable off-tunnel and actively misleading after a
# move to a new network (framework-pt, 2026-08-15).
IP="" IP=""
for i in 1 2 3 4 5; do for i in 1 2 3 4 5; do
IP=$(hostname -I 2>/dev/null | awk '{print $1}') IP=$(ip -4 route get 1.1.1.1 2>/dev/null | sed -n 's/.*src \([0-9.]*\).*/\1/p' | head -n1)
# Offline LAN (no default route): first address that is not a
# tunnel (10.44/16 WireGuard), CGNAT (100.64/10 Tailscale), or
# loopback one.
[ -n "$IP" ] || IP=$(hostname -I 2>/dev/null | tr ' ' '\n' \
| grep -vE '^(10\.44\.|100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.|127\.)' | head -n1)
[ -n "$IP" ] || IP=$(hostname -I 2>/dev/null | awk '{print $1}')
[ -n "$IP" ] && break [ -n "$IP" ] && break
sleep 2 sleep 2
done done
@@ -3269,6 +3281,11 @@ if [ -t 0 ] && [ -z "$ARCHIPELAGO_WELCOMED" ]; then
echo -e " ${OD}bitcoin node os${N}" echo -e " ${OD}bitcoin node os${N}"
if [ -n "$IP" ]; then if [ -n "$IP" ]; then
echo -e " ${W}web ui http://$IP${N}" echo -e " ${W}web ui http://$IP${N}"
# The mDNS name survives any DHCP change — it is the address to
# give people for a headless box that moves between networks.
if systemctl is-active avahi-daemon >/dev/null 2>&1; then
echo -e " ${W} http://$(hostname).local${N}"
fi
echo -e " ${W}ssh archipelago@$IP${N}" echo -e " ${W}ssh archipelago@$IP${N}"
echo -e " ${W}password archipelago (SSH)${N}" echo -e " ${W}password archipelago (SSH)${N}"
echo -e " ${OD}web ui asks you to create a password on first visit${N}" echo -e " ${OD}web ui asks you to create a password on first visit${N}"
@@ -107,6 +107,24 @@ server {
proxy_send_timeout 120s; proxy_send_timeout 120s;
} }
# AIUI Routstr proxy — the explicit, user-selected Routstr provider in
# AIUI's model picker. Same daemon, same session-gate discipline as the
# Claude block above; the daemon additionally refuses paid requests
# unless the operator has armed a Routstr budget (D-05), so this is
# never an open relay even though completions cost sats.
location /aiui/api/routstr/ {
proxy_pass http://127.0.0.1:5678;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Cookie $http_cookie;
proxy_buffering off;
proxy_cache off;
proxy_connect_timeout 120s;
proxy_read_timeout 300s;
proxy_send_timeout 120s;
}
# AIUI Ollama (local AI) proxy — same daemon, same session gate as above. # AIUI Ollama (local AI) proxy — same daemon, same session gate as above.
# The standalone AIUI OpenRouter relay that used to live here is deleted # The standalone AIUI OpenRouter relay that used to live here is deleted
# outright: the node holds no key for that backend, it is not in the # outright: the node holds no key for that backend, it is not in the
@@ -1041,6 +1059,20 @@ server {
proxy_read_timeout 300s; proxy_read_timeout 300s;
proxy_send_timeout 120s; proxy_send_timeout 120s;
} }
# Session-gated Routstr provider path — same rationale and shape as the
# HTTP server block above; both blocks must carry it (T-13-15).
location /aiui/api/routstr/ {
proxy_pass http://127.0.0.1:5678;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Cookie $http_cookie;
proxy_buffering off;
proxy_cache off;
proxy_connect_timeout 120s;
proxy_read_timeout 300s;
proxy_send_timeout 120s;
}
location /aiui/api/ollama/ { location /aiui/api/ollama/ {
proxy_pass http://127.0.0.1:5678; proxy_pass http://127.0.0.1:5678;
proxy_http_version 1.1; proxy_http_version 1.1;
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "neode-ui", "name": "neode-ui",
"version": "1.8.1-alpha", "version": "1.8.3-alpha",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "neode-ui", "name": "neode-ui",
"version": "1.8.1-alpha", "version": "1.8.3-alpha",
"dependencies": { "dependencies": {
"@scure/bip39": "^2.2.0", "@scure/bip39": "^2.2.0",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",

Some files were not shown because too many files have changed in this diff Show More