# STRVCT — Full Documentation STRVCT implements the naked objects pattern for JavaScript. You write domain model classes with annotated properties — the framework automatically generates the user interface, handles persistence, and keeps everything synchronized. See also: /llms.txt (curated index) and /sitemap.xml. --- Source: / A naked objects framework for JavaScript with automatic UI generation, persistence, and cloud sync. ## About STRVCT implements the [naked objects](http://downloads.nakedobjects.net/resources/Pawson%20thesis.pdf) pattern for JavaScript. You write domain model classes with annotated properties — the framework automatically generates the user interface, handles persistence, and keeps everything synchronized. The model layer is completely independent of the UI, allowing the same application code to run in a browser, on a command line, or headlessly in Node.js for testing and server-side processing. For a detailed treatment of this approach, see [Closing the Usability Gap in Naked Objects](docs/Naked%20Objects/index.html). ## Key Features - **Automatic UI** — Views are generated from the model graph using a Miller Column inspired navigation pattern. Custom views can override defaults when needed. - **Slot System** — Properties are declared as slots with annotations that control type checking, persistence, UI generation, view synchronization, and other features. - **Notification-Based Sync** — The model layer communicates to the views layer through deferred, deduplicated notifications with automatic sync loop detection. - **Transparent Persistence** — No custom serialization code required. Declare which objects and slots to persist, and the framework handles dirty tracking, automatic commits, and garbage collection via IndexedDB. - **Cloud Sync** — Object pools sync to Firebase Storage using a write-ahead log of delta files for efficient incremental updates. - **Gesture System** — Unifies mouse and touch input gestures. - **Internationalization** — AI-powered translation of UI text with no per-component wiring. The naked objects pattern means every slot value is translated at the model-to-view boundary automatically. - **Accessibility** — Automatic ARIA roles, labels, and states derived from the model. Slot metadata maps to aria-required, aria-valuemin/max, and aria-description; view classes emit landmark roles, live regions, and focus-visible styling. - **Content-Addressable Build** — Custom resource loading with content hashing for optimal caching and minimal network transfers. - Example App - FAQ - docs - Timeline - [GitHub Repository](https://github.com/stevedekorte/strvct.net) — Source code, issues, and contributions. For AI agents: [llms.txt](llms.txt) (curated index) and [llms-full.txt](llms-full.txt) (full content). --- Source: /docs/ # Documentation Guides, architecture, and API reference. - Naked Objects - Technical Overview - Implementation Overview - Slots - Getting Started - Lifecycle - Notifications - Views - Persistence - Events and Gestures - Accessibility - Internationalization - Services - Programming Idioms - Reference - Comparing to React - Inspirations - Future Work --- Source: /docs/Accessibility/ # Accessibility Automatic, framework-level ARIA support derived from the naked objects pattern. ## Overview STRVCT provides built-in accessibility support that works automatically for every application built on the framework. Because the UI is generated from model nodes through a small set of view classes, accessibility metadata is emitted at the framework level -- developers don't need to add ARIA attributes to individual components. This is a direct consequence of the naked objects architecture. In a conventional web framework, accessibility is a per-component concern: every button, list, and form field needs manual ARIA attributes, and the cost scales linearly with the number of components. In STRVCT, the same ~15 view classes render every application's UI, so fixing accessibility in those classes fixes it everywhere. ## Why the Naked Objects Pattern Helps Most frameworks require developers to manually add accessibility attributes to each component. STRVCT's architecture inverts this: since the framework generates views from the model, it can also generate accessibility metadata from the model -- automatically, in one place, for all applications. **The slot system already describes the data.** Slot metadata maps almost directly to ARIA attributes: | Slot metadata | ARIA attribute | |---|---| | `description()` | `aria-description` | | `isReadOnly()` | `aria-readonly` | | `isRequired()` | `aria-required` | | `slotType()` | informs the appropriate `role` | **JSON Schema annotations serve double duty.** The same annotations that generate JSON Schema for AI tool calls also drive accessibility attributes. A slot with `setMinimum(0)` and `setMaximum(100)` emits both JSON Schema constraints for AI and `aria-valuemin="0"` / `aria-valuemax="100"` for screen readers. The investment in richer slot annotations directly improves both AI integration and accessibility. **The node hierarchy maps to ARIA patterns.** The tile/stack navigation model (drill in, drill out, select with arrows) maps naturally to ARIA roles: | STRVCT concept | ARIA role | |---|---| | `SvTilesView` | `list` | | `SvTile` | `link` | | `SvFieldTile` | `group` | | Selected tile | `aria-current` | **Every new application inherits it.** When a developer creates a new `SvNode` subclass with slots, the framework automatically generates views with correct ARIA roles, labels, states, and metadata-derived attributes. No accessibility code required. ## Implementation ### ARIA Roles Each core view class emits an appropriate ARIA role: | View class | Role | Purpose | |---|---|---| | `SvTilesView` | `list` | Container for navigable tile items | | `SvTile` | `link` | Individual navigation item in a list | | `SvFieldTile` | `group` | Key-value field with label and value | | `SvBooleanFieldTile` | `checkbox` | Toggle field with checked/unchecked state | | `SvActionFieldTile` | `button` | Clickable action | | `SvOptionsNodeTile` | `listbox` | Container for selectable options | | `SvOptionNodeTile` | `option` | Individual selectable option | | `SvStackView` | `navigation` | Top-level navigation landmark | | `SvNavView` | `region` | Named content region | | `SvBreadCrumbsTile` | `navigation` | Breadcrumb navigation with `aria-label="Breadcrumb"` | ### Labels and Descriptions View classes derive ARIA labels from node metadata: | Source | ARIA usage | |---|---| | `node.nodeAriaLabel()` | `aria-label` on tiles, tile lists, and navigation regions | | `node.subtitle()` | `aria-description` on tiles | | `slot.description()` | `aria-description` on field tiles | | Key view text | `aria-label` on field tiles | `nodeAriaLabel()` is defined on `SvTitledNode` and returns `title()` by default. Domain objects can override it to provide a more descriptive label for screen readers without affecting the visible UI title. `subtitle()` and slot `description()` provide longer detail available on demand. ### Node-Level ARIA Overrides `SvTitledNode` provides methods that domain objects can override to customize accessibility behavior without modifying view code: | Method | Default | Purpose | |---|---|---| | `nodeAriaLabel()` | `title()` | Label announced by screen readers | | `nodeAriaRole()` | `null` (view's default) | Override the view class's default ARIA role | | `nodeAriaIsReadOnly()` | `null` (from slot editability) | Override read-only state | | `nodeAriaIsRequired()` | `null` (from slot `isRequired()`) | Override required state | Returning `null` from `nodeAriaRole()`, `nodeAriaIsReadOnly()`, or `nodeAriaIsRequired()` defers to the view's default behavior. Returning a value overrides it. This follows the same pattern as `setNodeViewClass()` -- the framework provides sensible defaults, the domain object overrides when it knows better. ### State Tracking ARIA states are updated dynamically through the framework's notification system: - **`aria-current`** -- Set to `"true"` on the selected tile, removed when deselected. The selected tile also receives focus. - **`aria-checked`** -- Updated on `SvBooleanFieldTile` when the boolean value changes. - **`aria-selected`** -- Updated on `SvOptionNodeTile` based on `isPicked()` state. - **`aria-disabled`** -- Set on `SvActionFieldTile` when the action's node is not enabled. - **`aria-readonly`** -- Set on `SvFieldTile` when the field is not editable. ### Slot Metadata to ARIA `SvFieldTile` includes a `syncAriaFromSlotMetadata()` method that reads slot annotations and emits corresponding ARIA attributes: | Slot annotation | ARIA attribute | |---|---| | `isRequired()` | `aria-required="true"` | | `getAnnotation("minimum")` | `aria-valuemin` | | `getAnnotation("maximum")` | `aria-valuemax` | | `description()` | `aria-description` | This wiring means that any slot configured with validation constraints automatically exposes those constraints to assistive technology. For example, a slot defined as: ```javascript { const slot = this.newSlot("hitPoints", 0); slot.setSlotType("Number"); slot.setIsRequired(true); slot.setAnnotation("minimum", 0); slot.setAnnotation("maximum", 999); slot.setDescription("Current hit points"); } ``` ...produces a field tile with `aria-required="true"`, `aria-valuemin="0"`, `aria-valuemax="999"`, and `aria-description="Current hit points"` -- with no accessibility-specific code from the developer. ### Live Regions Live regions announce dynamic content changes to screen readers without requiring focus. **Opt-in via nodes:** Any node can declare itself as a live region by implementing `nodeAriaLive()` returning `"polite"` or `"assertive"`. When `SvTilesView` syncs from a node with this method, it sets `aria-live` on the container. This is useful for status displays, notification lists, and progress indicators. **Direct on chat:** `SvChatMessageTile` sets `aria-live="polite"` directly, so new chat messages and AI responses are announced to screen readers as they arrive. ### Breadcrumb Navigation `SvBreadCrumbsTile` implements the ARIA breadcrumb pattern: - Container has `role="navigation"` and `aria-label="Breadcrumb"` - The last crumb (current location) is marked with `aria-current="location"` - Screen readers announce the breadcrumb trail as "Breadcrumb navigation" with the current item identified as "current location" ### Focus Management - **Tiles are programmatically focusable** via `tabindex=-1`. This makes them reachable by the framework's focus management without adding them to the Tab order. - **Focus indicators** use `:focus-visible` styling (2px solid cornflower blue outline) that appears only for keyboard navigation, not mouse clicks. - **High contrast mode** (`prefers-contrast: more`) increases the focus outline to 3px solid white for maximum visibility. - **Focus follows selection** -- when a tile is selected, it receives both `aria-current="true"` and DOM focus, ensuring screen readers announce the newly selected item. ### Visual Accessibility The framework's global CSS includes media query support for visual accessibility preferences: **Reduced motion** (`prefers-reduced-motion: reduce`): - All animations reduced to 0.01ms duration - All transitions reduced to 0.01ms duration - Animation iteration counts set to 1 - Scroll behavior set to auto (instant) **High contrast** (`prefers-contrast: more`): - Focus indicators enhanced to 3px solid white with tighter offset - Provides stronger visual distinction for keyboard navigation ### ARIA Landmarks Landmark roles provide structural navigation for screen readers: - `SvStackView` is marked as `navigation` -- the top-level navigation structure - `SvNavView` is marked as `region` with an `aria-label` derived from the current node's title - `SvBreadCrumbsTile` is marked as `navigation` with `aria-label="Breadcrumb"` Screen reader users can use landmark navigation (e.g., VoiceOver's VO+U landmarks menu) to jump between these regions. ## WCAG 2.2 Compliance The implementation targets WCAG 2.2 Level AA, which covers the requirements of most accessibility legislation (ADA, EAA, Section 508). ### Criteria Addressed | WCAG Criterion | How addressed | |---|---| | 1.3.1 Info and Relationships | ARIA roles and landmarks convey structure | | 1.3.2 Meaningful Sequence | DOM order matches visual order (flexbox layout) | | 2.1.1 Keyboard | Arrow keys, Enter, Escape for full navigation | | 2.3.3 Animation from Interactions | `prefers-reduced-motion` media query | | 2.4.1 Bypass Blocks | Landmark roles enable skip navigation | | 2.4.6 Headings and Labels | `aria-label` from node `title()` | | 2.4.7 Focus Visible | `:focus-visible` outline styling | | 3.2.1 On Focus | No context changes on focus | | 4.1.2 Name, Role, Value | ARIA roles, labels, and states on all interactive elements | ### Remaining for Full AA Compliance See [Accessibility Testing & Tuning](Future%20Work/Accessibility%20Testing%20%26%20Tuning/index.html) for details on remaining testing and tuning work, including VoiceOver validation, cross-screen-reader testing, focus management refinements, and per-application adjustments. ## Design Decisions ### ARIA Roles vs. Semantic Elements ARIA roles on div elements are equivalent to semantic HTML from the screen reader's perspective. Since STRVCT already handles keyboard activation and focus management on tiles, the native behaviors that semantic elements provide (space/Enter activation, default tab order) are redundant. Using ARIA attributes on existing div elements avoids disrupting existing CSS selectors and layout behavior. ### Default Roles from View Classes Default roles come from the view class, not the node, because the same node can appear in multiple views with different roles. For example, a node shown as a tile in a parent view has `role="link"`, while the same node as the root of its own tiles view has no tile role. Nodes can override the view's default role for cases where the node's semantics are more specific (e.g., `SvOptionsNodeTile` overrides to `listbox`). ### Label Priority | Source | ARIA attribute | Rationale | |---|---|---| | `title()` | `aria-label` | Short, instance-specific -- what the screen reader announces | | `subtitle()` | `aria-description` | Longer detail -- available on demand | | `jsonSchemaDescription()` | Fallback label | Describes the class when no instance title exists | | Node `description()` | Skipped | Intended for debugging, not user-facing | Slot-level `description()` (set via `setDescription()`) is distinct from node-level `description()` and is used for field-level `aria-description`. ## Files Modified The accessibility implementation touches the following framework files: | File | Changes | |---|---| | `_css.css` | `:focus-visible` styling, `prefers-reduced-motion`, `prefers-contrast` | | `SvTilesView.js` | `role="list"`, `aria-label`, opt-in `aria-live` | | `SvTile.js` | `role="link"`, `tabindex=-1`, `aria-label`, `aria-description`, `aria-current` | | `SvFieldTile.js` | `role="group"`, `aria-label`, `aria-readonly`, `syncAriaFromSlotMetadata()` | | `SvBooleanFieldTile.js` | `role="checkbox"`, `aria-checked` | | `SvActionFieldTile.js` | `role="button"`, `aria-label`, `aria-disabled` | | `SvOptionsNodeTile.js` | `role="listbox"` | | `SvOptionNodeTile.js` | `role="option"`, `aria-selected` | | `SvChatMessageTile.js` | `aria-live="polite"` | | `SvStackView.js` | `role="navigation"` | | `SvNavView.js` | `role="region"`, `aria-label` | | `SvBreadCrumbsTile.js` | `role="navigation"`, `aria-label="Breadcrumb"`, `aria-current="location"` | --- Source: /docs/Comparing%20to%20React/ # Comparing to React A guide for React developers exploring Strvct — what's different, what's similar, and when each approach makes more sense. ## The Core Difference React and Strvct solve the same fundamental problem — keeping a UI in sync with application state — but start from opposite ends. **React** is a view library. You describe what the UI looks like for a given state, and React makes the DOM match. Everything else — the model, persistence, routing, styling — is yours to choose. **Strvct** is a naked objects framework. You describe the domain model — its properties, relationships, and annotations — and the framework generates the UI, handles persistence, and manages navigation automatically. Custom views override the defaults when needed. They represent genuinely different philosophies about where developer effort should go. ## Architecture React is flexible about where it runs. A React app can be a client-side SPA, a server-rendered application, or a hybrid. The modern ecosystem includes server-side rendering (Next.js, Remix), static site generation, and server components — but none of these are required. Many React applications are purely client-side. Strvct is **local-first** by design. Data is persisted client-side in IndexedDB automatically, rendering happens entirely on the client, and the UI is responsive without waiting on network round trips. Cloud sync is available for backup and cross-device access, but the application works fully offline. ### What You Write In React, application code is primarily components — functions that return JSX describing the UI for a given state: ```jsx function Contact({ contact, onSave }) { const [name, setName] = useState(contact.name); const [email, setEmail] = useState(contact.email); return (
setName(e.target.value)} /> setEmail(e.target.value)} />
); } ``` This component handles rendering and editing but says nothing about persistence, navigation, or how the contact fits into a larger application. Those are separate concerns handled by other code — which is React's deliberate design choice. You compose the pieces you need. In Strvct, application code is primarily model classes — domain objects with annotated properties: ```javascript (class Contact extends SvStorableNode { initPrototypeSlots () { { const slot = this.newSlot("name", ""); slot.setSlotType("String"); slot.setShouldStoreSlot(true); slot.setSyncsToView(true); slot.setCanEditInspection(true); } { const slot = this.newSlot("email", ""); slot.setSlotType("String"); slot.setShouldStoreSlot(true); slot.setSyncsToView(true); slot.setCanEditInspection(true); } } initPrototype () { this.setTitle("Contact"); this.setShouldStore(true); } }.initThisClass()); ``` This defines the data model, persistence, and UI behavior in one place. The framework generates editable fields, navigation tiles, and handles storage from the slot annotations. The tradeoff: you get less control over exactly how things look, and the annotation-driven style takes some getting used to. ### State Management React uses unidirectional data flow. State lives in components (via `useState` or `useReducer`) or in external stores (Redux, Zustand, MobX). Views are pure functions of state. When state changes, React re-renders the affected component subtree and diffs a virtual DOM against the real DOM to compute minimal updates. This model is predictable and easy to reason about — given the same props, a component always renders the same output. Strvct uses an asymmetric notification pattern. Views hold direct references to their model objects and can send messages to them. Model objects post notifications when they change but have no knowledge of views. When a slot value changes, the setter posts a notification; views observing that node sync in response. The model layer functions independently of the UI — the same model runs headlessly in Node.js with no view layer at all. | | React | Strvct | |---|---|---| | State location | Components or external stores | Model objects (slots) | | Change detection | Virtual DOM diffing | Notification-based, per-slot | | Data flow | Unidirectional (state down, events up) | Asymmetric (views message models; models notify) | | Update granularity | Component subtree re-render | Individual slot/view sync | | Model-view coupling | Flexible (pure components to tightly coupled) | Strict separation (model has no knowledge of views) | | Headless execution | Requires mocking DOM APIs | Native (model runs without UI) | ### UI Generation React requires you to write every view explicitly. This gives full control over appearance, layout, and interaction — and it means the UI can look like anything. The cost is that each new entity type needs its own component, often with repeated patterns for forms, lists, and detail views. Strvct generates views from model annotations. A property marked `setCanEditInspection(true)` gets an editable field. A property marked as a subnode gets a navigable tile. The framework selects field types based on slot type. Custom views override the defaults by naming convention: create `ContactView` and the framework uses it for `Contact` nodes automatically. **React's strength**: Complete control over presentation. Bespoke UIs, data visualizations, highly branded experiences, and unconventional layouts are all straightforward. **Strvct's strength**: Consistent UI with minimal code. Applications with many model types get a working interface from model definitions alone. The tradeoff is that the generated UI follows the framework's visual conventions — stepping outside them requires custom views. ## Persistence React has no built-in persistence — you choose and integrate your own solution. This is intentional flexibility: REST APIs, GraphQL, Firebase, local storage, or ORMs all work. The tradeoff is integration effort and keeping the client model in sync with the persistence layer. Strvct includes persistence as a core feature. Mark a class with `setShouldStore(true)` and slots with `setShouldStoreSlot(true)`, and the framework handles serialization, IndexedDB storage, dirty tracking, and transactional commits automatically. Changes are batched and committed at the end of each event loop. **React's strength**: Freedom to choose the right persistence strategy for each use case. Server-rendered apps, offline-first PWAs, and real-time collaborative tools all have different needs. **Strvct's strength**: Zero-effort client-side persistence. Because data is stored locally, there are no network round trips between the user and their data. Cloud sync adds backup and cross-device access without replacing the local store. ## Navigation React relies on external routing libraries (React Router, Next.js routing). Routes map URL paths to components, and the resulting architecture supports deep linking, server-side rendering, and SEO naturally. Strvct generates navigation from the model graph. The UI is a Miller Column browser — selecting an item reveals its children in the next column. Breadcrumbs, column compaction on small screens, and keyboard navigation are built in. Adding a new level of hierarchy to the model automatically creates a new level of navigation. **React's strength**: URL-driven routing fits the web's architecture. Any navigation pattern — tabs, modals, drawers, wizards — can be implemented. **Strvct's strength**: Navigation is always consistent with the data structure, with no routing configuration to maintain. The limitation is that the Miller Column pattern works best for hierarchical data. Flat layouts, dashboards, or wizard flows require custom views. ## Internationalization React has no built-in i18n. Libraries like `react-intl` require maintaining separate translation files for each language and wrapping every visible string. Mature tooling exists for extraction, translation management, and CI checks. Strvct translates the UI automatically. Because all UI text flows from model slots through a single rendering pipeline, translation is injected at that boundary without per-component work. The framework uses AI-powered translation with persistent caching, so adding a language is a configuration change rather than a translation project. The tradeoff is that AI translation quality, while generally good, may not match professional human translation for all domains. ## Resource Loading React applications use standard ES module imports and bundlers (Vite, Webpack, esbuild). These provide source maps, hot module replacement, tree shaking, and code splitting. Cache invalidation relies on filename hashing. Strvct uses a custom Content-Addressable Memory (CAM) system. Resources are stored by SHA-256 content hash. On load, the framework compares hashes against a local cache and only downloads what's changed. This provides efficient content-based caching but means standard bundler tooling and source maps aren't available — debugging relies on sourceURL comments instead. ## Ecosystem and Tradeoffs This is where the two approaches differ most significantly. **React** has a vast ecosystem. Component libraries, dev tools, testing frameworks, extensive documentation, and a large developer community mean that most problems have documented solutions and available help. **Strvct** bundles its core functionality — UI generation, state management, persistence, navigation, resource loading, and caching — and includes a small number of third-party libraries as vendored source files rather than npm packages. This avoids package manager churn and ensures the pieces work together, but the tradeoff is real: fewer third-party integrations, less documentation, a non-standard module system that can't use npm packages directly, and a smaller community. When you hit a problem, you're more likely to need to read the framework source than find a Stack Overflow answer. | | React | Strvct | |---|---|---| | Component libraries | Thousands (MUI, Ant Design, etc.) | Built-in tile system | | State management | Redux, Zustand, MobX, Jotai, etc. | Built-in (slots + notifications) | | Persistence | Choose your own | Built-in (IndexedDB + cloud sync) | | Routing | React Router, Next.js, etc. | Built-in (model graph navigation) | | Build tooling | Vite, Webpack, esbuild, etc. | Built-in (CAM resource system) | | TypeScript | First-class support | Not used (dynamic evaluation) | | npm ecosystem | Full access | Not used (vendored external libs) | | Debugging | React DevTools, source maps | sourceURL-based, read the source | ## When to Consider Each ### React is likely the better choice when: - You need a highly custom or branded visual design - Your team benefits from React's well-documented patterns and large talent pool - You need server-side rendering or static site generation - SEO is critical - You want access to the npm ecosystem and third-party components - Your UI structure doesn't mirror a hierarchical domain model ### Strvct is worth considering when: - Your application is data-model-driven (admin tools, content management, domain-heavy apps) - You want persistence and UI generation without integration work - You want a working UI from model definitions, with custom views only where needed - You prefer a single cohesive framework over assembling pieces - You're building a local-first or offline-capable application - You want your model layer testable without a browser ### A note on SSR and SEO The "SEO is critical" and "needs server-side rendering" criteria above rule Strvct out for marketing sites, content platforms, and traditional e-commerce — cases where first-paint latency and search discoverability are competitive necessities. That category is smaller than the defaults of modern web frameworks make it appear. A large share of high-value consumer software today — ChatGPT, Claude, Cursor, Figma, Linear, Notion, Discord, Character.ai, Suno, Midjourney — is app-shell SPAs, where the logged-in product is the product and the marketing site, if there is one, sits on a different stack. For that class of product, SSR isn't load-bearing, and framework choices that center it (Next.js, Remix, server components) are solving a problem these apps don't have. Strvct targets this shape of software directly: an authenticated client-side application with heavy state, real-time interaction, and no meaningful SEO surface. ## Concepts Mapping For React developers getting oriented, here's how familiar concepts map: | React Concept | Strvct Equivalent | |---|---| | Component | SvNodeView (auto-generated or custom) | | Props | Slot values (accessed via getters) | | State (useState) | Slots with `setSyncsToView(true)` | | Context / prop drilling | SvNotificationCenter (publish/subscribe) | | useEffect | Notification observations | | Redux / Zustand | Slots + notification system (built in) | | React Router | Model graph navigation (automatic) | | JSX | No equivalent — UI generated from annotations | | Virtual DOM | No equivalent — direct DOM updates via SvNodeView | | npm install | No equivalent — external libs vendored as source | --- Source: /docs/Events%20and%20Gestures/ # Events and Gestures Event flow, listeners, gesture recognizers, timers, keyboard handling, and drag-and-drop. STRVCT provides two layers for handling user interaction: **event listeners** that wrap native DOM events and forward them to handler methods on views, and **gesture recognizers** that interpret sequences of events as higher-level gestures like taps, pans, and pinches. A managed **timer system** supports deferred work with both strong and GC-friendly weak timeouts. Most application code uses gesture recognizers rather than raw event listeners. The gesture system unifies mouse and touch input, handles multi-touch emulation on desktop, and coordinates competing gestures through a global `SvGestureManager`. - Event Flow - Event Listeners - Gesture Recognizers - Timers - Keyboard and Focus - Drag and Drop --- Source: /docs/Events%20and%20Gestures/Drag%20and%20Drop/ # Drag and Drop Browser drag-and-drop support for views and nodes, covering both drop targets and drag sources. ## Overview The browser's native drag-and-drop API is low-level and inconsistent — you must manually parse `DataTransfer` objects, handle file vs. string items differently, prevent the browser from navigating to dropped files, and deal with empty-space drops that bypass child elements entirely. STRVCT wraps this into a clean protocol: register a view for drops, and data arrives already parsed, typed by MIME, and routed to a handler method you name. You only implement the types you care about; everything else is silently ignored. The result is that adding drag-and-drop to a new view is typically a one-line registration plus one handler method. STRVCT wraps the HTML5 Drag and Drop API through two complementary systems: - **Drop targets** — Views register to accept dropped content (files, text, HTML from the desktop or other apps). Handled by `SvDomView_browserDragAndDrop` (a category on `SvDomView`) and `SvDropListener`. - **Drag sources** — Views register to be draggable, providing data when the user initiates a drag. Handled by the same category class plus `SvDragListener`. The system routes dropped data through a MIME-type dispatch chain, ultimately delivering it to either the view or its backing node. ## Key Files | File | Purpose | |------|---------| | `SvDomView_browserDragAndDrop.js` | Drop/drag API on all views | | `SvDropListener.js` | Drop event listener set | | `SvDragListener.js` | Drag event listener set | | `SvDocumentBody.js` | Global drop prevention | ## Drop Targets ### Registering a View for Drops ```javascript // In a view's init() method: this.setIsRegisteredForBrowserDrop(true); ``` This creates a `SvDropListener` that registers four event handlers on the view's DOM element: | DOM Event | Handler Method | |-----------|---------------| | `dragenter` | `onBrowserDragEnter(event)` | | `dragover` | `onBrowserDragOver(event)` | | `dragleave` | `onBrowserDragLeave(event)` | | `drop` | `onBrowserDrop(event)` | ### Accepting or Rejecting Drops Override `acceptsDrop(event)` to control whether the view accepts a drop: ```javascript acceptsDrop (event) { // Default implementation returns true. // Override to inspect event.dataTransfer.types, etc. return true; } ``` The return value controls both visual feedback (drag highlight) and whether the drop is processed. ### Drop Processing Chain When a drop is accepted, data flows through this chain: ``` onBrowserDrop(event) └── onBrowserDataTransfer(dataTransfer) ├── [files present] → onBrowserDropFile(file) for each file │ └── onBrowserDropMimeTypeAndRawData(mimeType, dataUrl) │ └── onBrowserDropChunk(dataChunk) │ └── dropMethodForMimeType(mimeType) → e.g. onBrowserDropImagePng(chunk) └── [items present] → getAsString → onBrowserDropChunk(dataChunk) └── (same MIME dispatch) ``` Each step can be overridden independently for custom handling. ### MIME Type Dispatch `dropMethodForMimeType()` converts a MIME type to a handler method name: | MIME Type | Method Name | |-----------|------------| | `image/png` | `onBrowserDropImagePng` | | `image/jpeg` | `onBrowserDropImageJpeg` | | `text/html` | `onBrowserDropTextHtml` | | `text/plain` | `onBrowserDropTextPlain` | | `application/json` | `onBrowserDropApplicationJson` | If the view doesn't implement the specific method, the drop is silently ignored. To handle all types, override `onBrowserDropChunk(dataChunk)` directly. ### Data Chunk Format Dropped data is wrapped in an `SvDataUrl` instance (`dataChunk`) that provides: - `mimeType()` — The content MIME type - `decodedData()` — The decoded string content - `setDataUrlString(url)` — For file drops (data URL format) ### Visual Feedback Override these for drag-over highlighting: ```javascript dragHighlight () { // Called when dragging over an accepting view. // Add visual indication (border, background, etc.) } dragUnhighlight () { // Called when drag leaves or drop completes. // Remove visual indication. } ``` ## Drag Sources ### Making a View Draggable ```javascript this.setIsRegisteredForBrowserDrag(true); // This also calls setDraggable(true) on the element. ``` This registers a `SvDragListener` for `dragstart`, `drag`, and `dragend` events. ### Providing Drag Data Override `onBrowserDragStart(event)` to set data on the transfer: ```javascript onBrowserDragStart (event) { event.dataTransfer.setData("text/plain", this.node().title()); event.dataTransfer.setData("application/json", JSON.stringify(this.node().asJson())); return true; // return false to cancel the drag } ``` ## Node Integration Views (particularly `SvTilesView`) forward drop data to their backing node: ```javascript // In SvTilesView: onBrowserDropChunk (dataChunk) { const node = this.node(); if (node && node.onBrowserDropChunk) { node.onBrowserDropChunk(dataChunk); } this.scheduleSyncFromNode(); } ``` Nodes implement `onBrowserDropChunk(dataChunk)` to handle the import: ```javascript // In a node class: onBrowserDropChunk (dataChunk) { if (dataChunk.mimeType() === "application/json") { const json = JSON.parse(dataChunk.decodedData()); const item = MyItemClass.clone().deserializeFromJson(json, null, []); this.addSubnode(item); } } ``` ## SvScrollView Drop Delegation Drop events fire on the innermost DOM element under the cursor and bubble upward. When a child view's element doesn't fill its parent container, drops in the empty space land on the parent and bypass the child. `SvScrollView` solves this by registering for drops itself and delegating to its content view: ```javascript // SvScrollView: acceptsDrop (event) { const cv = this.scrollContentView(); return cv && cv.acceptsDrop ? cv.acceptsDrop(event) : false; } onBrowserDrop (event) { const cv = this.scrollContentView(); if (cv) { return cv.onBrowserDrop(event); } event.preventDefault(); return false; } ``` This ensures drops anywhere in the scroll area — including empty space below the content — are handled by the content view (typically SvTilesView). ## SvDocumentBody Safety Net `SvDocumentBody` registers for drops globally on `document.body` to prevent the browser's default behavior of navigating to dropped files: ```javascript // SvDocumentBody: acceptsDrop (event) { event.preventDefault(); // prevent browser from opening the file return false; // reject the drop } ``` If a child view handles the drop first and calls `stopPropagation()`, SvDocumentBody never sees the event. ## Summary | Concept | Class/Method | Purpose | |---------|-------------|---------| | Register for drops | `setIsRegisteredForBrowserDrop(true)` | Listen for drag/drop events | | Accept/reject | `acceptsDrop(event)` | Control which drops are handled | | Process drop | `onBrowserDropChunk(dataChunk)` | Handle dropped data | | MIME routing | `dropMethodForMimeType(mimeType)` | Route to type-specific handler | | Visual feedback | `dragHighlight()` / `dragUnhighlight()` | Show drop zone state | | Make draggable | `setIsRegisteredForBrowserDrag(true)` | Enable as drag source | | Provide drag data | `onBrowserDragStart(event)` | Set transfer data | | Node integration | `node.onBrowserDropChunk(chunk)` | Forward drops to model | | Safety net | `SvDocumentBody.acceptsDrop()` | Prevent browser file open | | Scroll delegation | `SvScrollView.onBrowserDrop()` | Forward drops in empty space | --- Source: /docs/Events%20and%20Gestures/Event%20Flow/ # Event Flow The end-to-end path from a browser event to an application handler. ## Overview Raw DOM events are too low-level for most application code. Distinguishing a tap from a long-press from a pan requires tracking timing, movement thresholds, and finger counts — and when multiple gestures compete on the same element, the browser offers no arbitration. STRVCT's event pipeline absorbs this complexity: raw events feed into gesture recognizer state machines, a central SvGestureManager resolves conflicts (so a tap and a long-press on the same view don't both fire), and the application only sees clean, high-level callbacks like `onTapComplete` or `onPanMove`. Views that don't need gestures can still handle raw events directly — the gesture layer is additive, not mandatory. ``` Browser DOM Event → SvEventListener receives it → calls named method on delegate (the view) → if a SvGestureRecognizer is listening, updates its state machine → when conditions are met, requests activation from SvGestureManager → if accepted, sends delegate messages to the view → view handler executes application logic ``` For raw event listeners (without gestures), the handler is called directly on the view at step 2, skipping the gesture layer. ## Detailed Steps 1. **Browser fires a native DOM event** (mouse, touch, keyboard, etc.) on a DOM element. 2. **An `SvEventListener` receives it.** The listener was registered on the view's element via `addEventListener()`. It calls the corresponding method on its delegate — typically the view itself. For example, a `mousedown` event calls `onMouseDown(event)` on the view. 3. **Returning `false` stops propagation.** If the handler returns `false`, `event.stopPropagation()` is called, preventing the event from bubbling to ancestor elements. 4. **Gesture recognizers observe the event.** If the view has gesture recognizers attached, they receive the raw event and update their internal state machines. For example, a `SvTapGestureRecognizer` tracks mousedown/mouseup timing and position. 5. **The recognizer requests activation.** When the gesture's conditions are met (correct finger count, sufficient movement, hold duration, etc.), it calls `SvGestureManager.requestActiveGesture()`. 6. **SvGestureManager arbitrates.** The singleton manager checks whether another gesture is already active, considers the view hierarchy, and decides whether to accept or reject the request. Competing gestures on the same or ancestor views are cancelled. 7. **The recognizer sends delegate messages.** If accepted, the recognizer calls phase methods on the view: `onGestureBegin`, `onGestureMove`, `onGestureComplete`, or `onGestureCancelled`. 8. **The view executes application logic.** The handler method runs whatever the application needs — updating model state, triggering animations, navigating, etc. ## Special Cases - **Keyboard events** route through `SvKeyboard`, which generates modifier-aware method names (e.g., `onShiftAKeyDown`). - **Drag-and-drop events** route through `SvDropListener`/`SvDragListener` and the MIME dispatch chain in `SvDomView_browserDragAndDrop`. - **Focus events** interact with the responder chain in `SvResponderDomView`. --- Source: /docs/Events%20and%20Gestures/Event%20Listeners/ # Event Listeners Thin wrappers around native DOM events that forward to handler methods on views. ## Architecture Calling `addEventListener` directly ties views to specific DOM event names and requires manual bookkeeping — tracking which listeners are registered, removing them at the right time, and ensuring handlers exist before registering. Event listeners centralize this: they register lazily on first use, group related events (e.g., all mouse events) into a single toggle, dispatch to named methods on the view, and clean up automatically when the view is retired. This keeps views focused on handling events rather than managing listener lifecycle. Event listening is built on two base classes: - **`SvEventListener`** — Wraps a single DOM event. Registers with a target element, and when the event fires, calls a named method on its delegate (typically the view). - **`SvEventSetListener`** — Groups related `SvEventListener` instances. Subclasses define which events they cover via `setupListeners()`. Views manage their listeners through `SvListenerDomView`, which maintains a map of listener class names to listener instances. Listeners are created lazily on first access and cleaned up when the view is removed. ## Available Listeners | Listener | Events | |----------|--------| | `SvMouseListener` | mousedown, mouseup, mouseover, mouseleave, click, dblclick, contextmenu | | `SvMouseMoveListener` | mousemove (separate to avoid overhead when not needed) | | `SvTouchListener` | touchstart, touchmove, touchcancel, touchend | | `SvTouchMoveListener` | touchmove (separate for same reason) | | `SvKeyboardListener` | keydown, keyup, input | | `SvFocusListener` | focus, blur, focusin, focusout | | `SvDragListener` | dragstart, drag, dragend | | `SvDropListener` | dragenter, dragover, dragleave, drop | | `SvScrollListener` | scroll | | `SvWheelListener` | wheel | | `SvClipboardListener` | copy, cut, paste | | `SvSelectListener` | select | | `SvWindowListener` | resize | | `SvAnimationListener` | animationstart, animationend, animationiteration | | `SvTransitionListener` | transitionend | | `SvGamePadListener` | gamepadconnected, gamepaddisconnected | ## Registering for Events Views provide convenience methods for common event families: ```javascript view.setIsRegisteredForMouse(true); // onMouseDown, onMouseUp, etc. view.setIsRegisteredForKeyboard(true); // onKeyDown, onKeyUp view.setIsRegisteredForFocus(true); // onFocus, onBlur view.setIsRegisteredForClicks(true); // uses a SvTapGestureRecognizer internally view.setIsRegisteredForWindowResize(true); view.setIsRegisteredForBrowserDrop(true); ``` Event handler methods are called by name on the view. Returning `false` from a handler calls `event.stopPropagation()` to prevent the event from bubbling. ## Listener Lifecycle Listeners are created lazily when first accessed via `SvListenerDomView.listenerNamed()`: ```javascript listenerNamed (className) { const map = this.eventListenersMap(); if (!map.has(className)) { const instance = Object.getClassNamed(className).clone() .setListenTarget(this.element()) .setDelegate(this); map.set(className, instance); } return map.get(className); } ``` The listener target is the view's DOM element. The delegate is the view itself. `SvEventSetListener.start()` iterates its child `SvEventListener` instances, each of which: 1. Checks `delegateCanRespond()` — verifies the delegate has the handler method 2. Calls `addEventListener()` on the target element with the configured options `stop()` removes all event listeners. `setIsListening(bool)` toggles between started and stopped states. If the listen target, delegate, or capture mode changes while listening, the listener automatically stops, updates its configuration, and restarts via `resync()`. ## Cleanup Listeners are automatically removed when a view is retired from the hierarchy. The view's `prepareToRetire()` calls `removeAllListeners()`, which stops every registered `SvEventSetListener` and clears the listener map. No manual cleanup is needed for views that follow normal lifecycle patterns. --- Source: /docs/Events%20and%20Gestures/Gesture%20Recognizers/ # Gesture Recognizers Higher-level state machines that interpret sequences of events as meaningful gestures. ## Unified Input The browser provides two separate event families for pointing — mouse events on desktop and touch events on mobile — with different semantics, coordinate systems, and lifecycle patterns. Without an abstraction layer, every interactive view needs parallel code paths for both input types, plus logic to prevent ghost clicks and handle edge cases where both fire simultaneously. Gesture recognizers eliminate this. They consume raw mouse and touch events internally and present a single, input-agnostic interface to the view. A `SvPanGestureRecognizer` works identically whether driven by a mouse drag or a finger swipe — the view implements `onPanBegin` once and it works on every device. This is not just a convenience; it means new interaction features are automatically cross-platform from the first line of code, with no per-device testing or branching required. Mouse input naturally supports only single-finger gestures (tap, pan, long-press, slide), since a mouse has one cursor. Multi-finger gestures like pinch and rotation require touch input — or the Shift+click emulation described below, which simulates a second finger for desktop testing. ## How Gestures Work A gesture recognizer is a state machine that observes raw events on a view and interprets them as a gesture with defined phases: **begin**, **move**, **complete**, or **cancelled**. Each recognizer is attached to a view via `addGestureRecognizer()`. When the gesture's conditions are met (correct finger count, sufficient movement, etc.), the recognizer sends delegate messages to the view: ``` acceptsGestureType(gesture) — optional: can the view accept this gesture? onGestureTypeBegin(gesture) — gesture recognized and started onGestureTypeMove(gesture) — gesture is in progress onGestureTypeComplete(gesture) — gesture finished successfully onGestureTypeCancelled(gesture) — gesture was interrupted ``` For example, a `SvPanGestureRecognizer` sends `onPanBegin`, `onPanMove`, `onPanComplete`, and `onPanCancelled`. ## Available Gestures | Recognizer | Detects | Key Configuration | |------------|---------|-------------------| | `SvTapGestureRecognizer` | Single or multi-tap | `numberOfTapsRequired`, `numberOfFingersRequired`, `maxHoldPeriod` | | `SvPanGestureRecognizer` | Click-and-drag | `minNumberOfFingersRequired`, `maxNumberOfFingersAllowed` | | `SvPinchGestureRecognizer` | Two-finger pinch/zoom | Fixed to 2 fingers | | `SvRotationGestureRecognizer` | Two-finger rotation | Fixed to 2 fingers | | `SvLongPressGestureRecognizer` | Press-and-hold | `timePeriod` (default 500ms) | | `SvSlideGestureRecognizer` | Directional slide | `direction` ("left", "right", "up", "down"), `maxPerpendicularDistToBegin` | | `SvEdgePanGestureRecognizer` | Pan from view edge | Subclasses for left, right, top, bottom | | `SvScreenEdgePanGestureRecognizer` | Pan from screen edge | Subclasses for left, right, top, bottom | ## Adding Gestures to a View Common gestures have convenience methods: ```javascript view.addDefaultTapGesture(); // single tap view.addDefaultDoubleTapGesture(); // double tap view.addDefaultPanGesture(); // drag ``` For custom configuration, clone a recognizer and configure it: ```javascript const gesture = SvLongPressGestureRecognizer.clone(); gesture.setTimePeriod(800); // 800ms hold view.addGestureRecognizer(gesture); // implement onLongPressComplete(gesture) on the view ``` ## SvGestureManager `SvGestureManager` is a singleton that coordinates competing gestures globally. When a gesture requests activation, the manager decides whether to accept it based on: - Whether another gesture is already active - View hierarchy (child views can steal control from parent views) - Whether the existing active gesture accepts cancellation This prevents conflicts — for example, a tap gesture and a pan gesture on the same view won't fire simultaneously. The first gesture to request activation after meeting its conditions wins, and competing gestures are cancelled. Views can also explicitly cancel gestures: ```javascript view.cancelAllGesturesExcept(panGesture); ``` ## Cleanup and Lifecycle Gesture recognizers are automatically cleaned up when a view is retired. When a view loses its parent and is removed from the view hierarchy, its `prepareToRetire()` method runs a three-step teardown: 1. **`removeAllGestureRecognizers()`** — Each recognizer is stopped (removing its event listeners) and its view target is cleared. 2. **`removeAllListeners()`** — All event listeners registered on the view's element are removed. 3. **`cancelAllTimeouts()`** — Any pending timers (such as long-press delays) are cancelled. Individual gestures can also be removed at any time with `removeGestureRecognizer(gesture)`, which stops the recognizer and detaches it from the view. Some recognizers set `shouldRemoveOnComplete(true)` to automatically remove themselves after firing once — useful for one-shot gestures. This means application code rarely needs to manage gesture cleanup manually. As long as views are properly removed from the hierarchy, all gesture state is released automatically. ## Garbage Collection Modern JavaScript engines use mark-and-sweep garbage collection, which correctly handles circular references. When a view is removed from its parent and no other rooted object references it, the entire cluster — view, gesture recognizers, and element-level event listeners — becomes unreachable and is collected automatically without explicit cleanup. The main GC concern is listeners registered on **rooted targets** like `window` or `document`, since those targets' internal listener registries hold strong references back to the callback (and through it, the gesture and view). Most gesture recognizers only attach `window` listeners temporarily during active tracking (between the down/press and up/finish events), so they clean up naturally when the gesture completes. `SvScreenEdgePanGestureRecognizer` is the exception — it registers permanent `window` listeners and must be explicitly stopped or removed. Timers (`setTimeout`) also root their target until they fire. Gesture recognizers use `addWeakTimeout()` for their internal timers (deactivation delays, long-press detection, etc.), which holds only a `WeakRef` to the target — allowing the object to be collected if it becomes unreachable, with the timer silently becoming a no-op. ## Multi-Touch Emulation On desktop, holding Shift while clicking simulates a second finger. This allows testing pinch, rotation, and other multi-touch gestures without a touch device. The emulation is handled transparently by the gesture recognizer base class. --- Source: /docs/Events%20and%20Gestures/Keyboard%20and%20Focus/ # Keyboard and Focus Keyboard state tracking, modifier-aware key dispatch, and the responder chain for focus management. ## SvKeyboard The browser's native keyboard handling has two gaps that matter for an application framework. First, `keydown` and `keyup` events tell you which key was just pressed but not which other keys are already held — there is no built-in API for querying current keyboard state. Second, the DOM's `focus` model is element-level: it knows which element has focus, but has no concept of a focus chain through a view hierarchy, tab-order navigation between views, or a centralized way for views to declare whether they accept keyboard input. `SvKeyboard` and the responder chain address these: a singleton tracks live key state and generates modifier-aware method names (so views handle `onShiftAKeyDown` instead of parsing modifier flags), while `SvResponderDomView` layers a responder chain on top of DOM focus to manage keyboard routing through the view tree. `SvKeyboard` is a singleton that tracks global keyboard state — which keys are currently pressed, modifier key status, and key code/name mapping. ## Modifier-Aware Method Names When a key event fires, `SvKeyboard` generates a method name that includes active modifiers. For example, pressing Shift+A generates a call to `onShiftAKeyDown()`. This allows views to handle specific key combinations by simply implementing the named method. The generated method names follow the pattern: `on[Modifiers][KeyName]Key[Down|Up]`. ## Platform Differences - macOS: Command key maps to MetaLeft/MetaRight - All platforms: Control, Shift, Alt (Option on macOS) - Some OS-level shortcuts cannot be intercepted by the framework ## Responder Chain `SvResponderDomView` implements a responder chain for managing keyboard focus: - **`acceptsFirstResponder`** — Whether a view can receive keyboard focus. - **`becomeFirstResponder()`** / **`releaseFirstResponder()`** — Acquire or release focus. - **`nextKeyView`** — The next view in the tab order, enabling Tab-key navigation between focusable views. The responder chain ensures that keyboard events are routed to the focused view, and provides hooks (`willBecomeFirstResponder`) for views to prepare when receiving focus. --- Source: /docs/Events%20and%20Gestures/Timers/ # Timers Managed timeout system for scheduling deferred work, with support for weak references to avoid preventing garbage collection. ## Object_timeouts All objects in STRVCT inherit timeout management through the `Object_timeouts` category on `Object`. This provides named, trackable timeouts that can be cancelled individually or in bulk — replacing direct `setTimeout` calls throughout the framework. Direct `setTimeout` calls are fire-and-forget: there is no built-in way to cancel them by name, deduplicate them, or cancel all pending timers on an object at once. Worse, because the browser's timer queue holds a strong reference to the callback closure — which typically captures `this` — the target object cannot be garbage collected until every pending timer fires, even if nothing else references it. `Object_timeouts` solves both problems: named timeouts with automatic deduplication prevent timer accumulation, and the `addWeakTimeout` variant breaks the strong reference chain so that unreachable objects can be collected without waiting for their timers. ### Basic Usage ```javascript // Fire after a delay this.addTimeout(() => this.doSomething(), 1000); // Named timeout (replaces any existing timeout with the same name) this.addTimeout(() => this.poll(), 5000, "pollTimer"); // Cancel a named timeout this.clearTimeoutNamed("pollTimer"); // Cancel all timeouts on this object this.cancelAllTimeouts(); ``` ### Strong vs Weak Timeouts The framework provides two timeout methods with different garbage collection behavior: **`addTimeout(func, delay, name)`** — The standard timeout. The browser's timer queue holds a strong reference to the callback closure, which typically captures `this`. This **prevents the object from being garbage collected** until the timer fires. Use this when the work must happen regardless of the object's lifecycle — network retries, resource cleanup, message delivery guarantees. **`addWeakTimeout(func, delay, name)`** — A GC-friendly timeout. Stores the callback on the object itself and uses only a `WeakRef` in the `setTimeout` closure. If the object becomes unreachable before the timer fires, it can be collected and the timer silently becomes a no-op. Use this for work that only matters while the object is alive — UI updates, animations, gesture state management. ```javascript // Strong: retry must happen even if no one holds a reference to this request this.addTimeout(() => this.retryRequest(), 5000); // Weak: animation only matters if the view still exists this.addWeakTimeout(() => this.fadeOut(), 300); ``` ### How Weak Timeouts Work A regular `setTimeout` closure captures `this`, creating a strong reference chain from the browser's timer queue to the object. This prevents garbage collection even if nothing else references the object. `addWeakTimeout` breaks this chain: 1. The callback is stored on the object in a `weakTimeoutCallbackMap` (keyed by Symbol) 2. The `setTimeout` closure captures only a `WeakRef` to the object and the Symbol key 3. When the timer fires, it calls `deref()` on the `WeakRef` 4. If the object was collected, `deref()` returns `undefined` and the timer is a no-op 5. If the object is still alive, the callback is retrieved from the map and executed The callback itself may capture `this` via a closure (e.g., `() => this.doSomething()`), but since it is stored *on the object*, this creates a reference cycle rather than a rooted chain. The cycle is collectible by the mark-and-sweep GC when no external root references the object. ### When to Use Which | Scenario | Method | Reason | |----------|--------|--------| | Network request retry | `addTimeout` | Retry must fire to avoid silent failures | | Polling for external state | `addTimeout` | External operation continues regardless | | Resource cleanup (e.g., revoking blob URLs) | `addTimeout` | Leak prevention must happen | | Peer connection heartbeat | `addTimeout` | Connection health requires it | | View animation timing | `addWeakTimeout` | No view = nothing to animate | | Gesture deactivation delay | `addWeakTimeout` | No gesture = nothing to deactivate | | UI scroll/focus scheduling | `addWeakTimeout` | No view = nothing to scroll or focus | | Coach mark display delay | `addWeakTimeout` | No view = nothing to display | ### Naming and Deduplication When a name is provided, `addTimeout` and `addWeakTimeout` both cancel any existing timeout with the same name before scheduling the new one. This prevents accumulation of duplicate timers: ```javascript // Each call replaces the previous "autoSave" timer this.addTimeout(() => this.save(), 5000, "autoSave"); ``` ### Bulk Cancellation `cancelAllTimeouts()` cancels every pending timeout (both strong and weak) on the object. This is called by `prepareToRetire()` during view teardown, but can also be called manually when resetting an object's state. ## Integration with Views Gesture recognizers and views use `addWeakTimeout` for their internal timers: - **`SvGestureRecognizer.didFinish()`** — Deferred deactivation after gesture completes - **`SvLongPressGestureRecognizer`** — Long-press detection timer - **`SvTapGestureRecognizer`** — Maximum hold period before cancellation - **`SvDomView_animations`** — CSS transition timing and sequencing - **`SvResponderDomView`** — Deferred focus management These timers do not prevent their views from being garbage collected if the view is removed from the hierarchy. --- Source: /docs/Future%20Work/ # Future Work Design ideas, architectural discussions, and potential future improvements. - Async Final Initialization - Lazy Slots - Promise-Wrapped Slots - JSON Schema - Complete Protocols - Tile Views - Tile Container Views - Emergent Capabilities - Accessibility Testing & Tuning - Async Navigation - Graph Database --- Source: /docs/Future%20Work/Accessibility%20Testing%20%26%20Tuning/ # Accessibility Testing & Tuning Validating and refining the framework's ARIA implementation across screen readers, devices, and applications. ## Overview The core accessibility infrastructure is in place -- ARIA roles, labels, states, landmarks, live regions, focus indicators, and slot-to-ARIA metadata wiring are all implemented in the framework's view classes (see [Accessibility](../../Accessibility/index.html) documentation). What remains is testing that implementation against real assistive technology and tuning behavior for edge cases that only surface during actual screen reader use. ## VoiceOver Testing macOS VoiceOver is the primary testing target since it's built into the development platform. - Walk through a representative application with VoiceOver enabled (Cmd+F5) - Verify that tile navigation announces tile content (`aria-label` from `title()`) - Verify that field tiles announce their role, label, and current value - Verify that boolean fields announce as checkboxes with checked state - Verify that action fields announce as buttons with enabled/disabled state - Verify that option lists announce as listbox with selected option - Verify that breadcrumbs announce as navigation with current location - Verify that `aria-live` regions announce new content (chat messages, status updates) - Verify that landmark roles provide useful navigation shortcuts (VO+U landmarks menu) ## Tab Key / Screen Reader Interaction The framework overrides native Tab behavior with a custom `nextKeyView` chain. This may conflict with screen reader conventions. - Test whether VoiceOver's Tab navigation (VO+Tab) works correctly with `tabindex=-1` tiles - Determine if the `nextKeyView` chain needs to be suspended when a screen reader is detected - Investigate `aria-activedescendant` as an alternative to physical focus movement for tile navigation - Verify that form fields within tiles receive focus correctly when Tab is pressed ## Navigation Announcement Mechanism The policy is established: announce programmatic navigation, stay silent on user-initiated navigation. The mechanism needs implementation. - Investigate whether tile-selection navigation and programmatic navigation go through different code paths in `SvStackView` / `SvNavView` - Determine how to detect "this focus change was not a direct response to a tile selection" - Implement `aria-live` announcements for programmatic navigation (e.g., "Start Session" navigating to Narration view) - Test that user-initiated drill-in/drill-out does not produce redundant announcements ## Cross-Screen-Reader Testing Different screen readers have different behaviors and ARIA support levels. - **NVDA on Windows** -- the most popular free screen reader; test with Firefox and Chrome - **JAWS on Windows** -- the most widely used commercial screen reader - **TalkBack on Android** -- test with Chrome on Android - Verify that ARIA roles, states, and properties work consistently across readers - Document any reader-specific workarounds needed ## Per-Application Adjustments The framework provides good defaults, but some content needs application-level attention. - **Alternative text for images** -- the framework can't infer what a picture shows; applications must provide `alt` text for meaningful images - **Custom views** -- any view that overrides default rendering may need custom ARIA attributes - **Domain-specific labels** -- where auto-generated slot descriptions aren't clear enough, applications should override `title()` or provide `aria-label` overrides - **Complex widgets** -- application-specific composite widgets (e.g., dice rollers, map views) need manual ARIA patterns ## Color Contrast Verification The default dark theme has been reviewed for WCAG 4.5:1 contrast compliance. Remaining work: - Run automated contrast checks (axe, Lighthouse) against a running application - Verify contrast ratios for all interactive states (hover, focus, disabled, selected) - Check contrast in light theme variants if applicable - Verify that `prefers-contrast: more` media query produces sufficient enhancement - Ensure information is not conveyed by color alone -- use shape, text, or pattern as reinforcement ## Text Resize Testing WCAG 1.4.4 requires content to be usable at 200% text size. - Test with browser zoom at 200% - Verify that content reflows correctly at 320px equivalent viewport width (WCAG 1.4.10) - Check that no content is clipped or overlapping at increased text sizes - Verify that STRVCT's flexbox-based layout accommodates text expansion gracefully ## Focus Management Refinements - **Focus trapping in modals** -- when a modal dialog is open, Tab should cycle within the dialog - **Focus restoration** -- drilling out should restore focus to the tile that was drilled into - **Skip links** -- provide a way to skip past repetitive navigation to main content - Verify that focus is visible and meets WCAG 2.2 contrast requirements (3:1 against adjacent colors) in all themes ## Automated Regression Testing - Evaluate integrating axe-core or similar into the test suite for automated ARIA validation - Consider adding Playwright accessibility assertions for critical navigation paths - Set up CI checks that flag new accessibility regressions --- Source: /docs/Future%20Work/Async%20Final%20Initialization/ # Async Final Initialization Adding an async phase to the object initialization lifecycle. ## Context When objects need async data during initialization (e.g., fetching from cloud storage, API calls), the current synchronous initialization flow (`init()` → `finalInit()` → `afterInit()`) doesn't accommodate this cleanly. ## The Idea Add an `asyncFinalInit()` phase that runs after synchronous initialization but before the object is considered "fully ready." ## Potential Benefits - Clean architectural pattern — async initialization gets its own explicit phase - Objects have a clear "fully ready" state - Generalizes to many async initialization scenarios - Could handle lazy-loaded cloud data, API-dependent initialization, etc. ## Challenges to Solve ### Initialization State Tracking - Objects need an `isFullyInitialized` flag - Anything accessing them needs to check or wait for completion - Views need to handle "initializing" state gracefully (loading indicators, disabled interactions) ### Cascading Dependencies - If object A's `asyncFinalInit` depends on object B's `asyncFinalInit`, ordering becomes complex - Need dependency resolution or explicit waiting mechanisms - Risk of deadlocks if dependencies are circular ### Validation Timing - Current validation runs synchronously and may depend on async data - Options to consider: - Defer all validation until after `asyncFinalInit` - Add a `validationReady` state that skips validation when false - Make validation itself async-aware - Example: Options dropdown validation needs character names, but characters are lazy-loaded stubs ### Persistence Edge Cases - What happens if object is accessed/modified before `asyncFinalInit` completes? - Should writes be queued? Rejected? Merged later? - How does this interact with the dirty tracking system? ### UI Synchronization - Views may render before async data is ready - Need consistent pattern for showing loading states - Re-render triggers when `asyncFinalInit` completes ## Alternative Approaches Considered **Manifest-Based Metadata:** For the specific case of lazy-loaded cloud objects, storing display metadata (title, subtitle) in a manifest allows stubs to display correctly without fetching full data. The data is synchronously available — just stored in a different place. - Pros: No framework changes, uses existing infrastructure - Cons: Only works when you control what metadata goes in the manifest **Async Closures for Options:** Add `validItemsAsyncClosure` to slots/options that returns a Promise. The options node shows "Loading..." until the closure resolves. - Pros: Localized to options, doesn't affect whole initialization flow - Cons: Adds complexity to Slot and SvOptionsNode, may be over-engineering for limited use cases - Note: This was implemented and then removed in favor of the manifest approach ## Open Questions - How common is async initialization across the codebase? Is framework support warranted? - Should `asyncFinalInit` be opt-in per class, or a universal phase? - How would this interact with the persistence system's `loadFromRecord` flow? - Could a simpler "initialization complete" event/notification pattern suffice? ## Related Patterns in Other Frameworks - React's `useEffect` for async operations after render - SwiftUI's `.task` modifier for async work tied to view lifecycle - Android's `ViewModel` with `init` blocks and coroutine scopes --- Source: /docs/Future%20Work/Async%20Navigation/ # Async Navigation Rearchitecting the navigation stack to support asynchronous loading at every level. ## Status This is a long-term architectural direction, not expected to happen soon. The current synchronous navigation model works well for the common case and is significantly simpler to reason about. This page captures the motivation and tradeoffs for future reference. ## Context STRVCT's navigation stack is currently synchronous. When a user drills into a node, its subnodes are expected to be available immediately in memory -- the tile container reads the subnode array, creates tiles, and displays them in the same frame. This synchronous guarantee makes the navigation code straightforward: there are no loading states, no cancellation of in-flight requests, no partial renders, and no race conditions between navigation events. However, this model assumes all data is local. As the framework is used with cloud-backed data, server-side collections, or very large hierarchies, the assumption that subnodes are always available breaks down. Async navigation would allow any level of the hierarchy to load its children on demand -- from a remote API, a lazy database query, or any other asynchronous source. ## What Would Change The synchronous contract runs deep through the navigation stack: - **`SvStackView`** assumes column content is ready when a navigation event fires. - **`SvNavView`** assumes it can read a node's subnodes and build tiles immediately. - **`SvTilesView`** assumes the subnode array is complete and in memory. - **Tile creation** assumes the node it represents is fully initialized. An async-capable navigation stack would need to handle the possibility that any of these steps returns a promise rather than a value. This means: - **Loading states**: Each navigation level needs a placeholder (spinner, skeleton tiles, or empty state) while data loads. - **Cancellation**: If the user navigates away before loading completes, in-flight requests must be cancelled to avoid stale data arriving in the wrong column. - **Error handling**: Network failures, timeouts, and partial results need UI treatment at every level, not just at the data layer. - **Progressive rendering**: Results may arrive incrementally -- the container needs to render partial subnode sets and update as more arrive. - **Back-navigation caching**: Previously loaded levels should be cached so drilling out doesn't re-fetch. ## Performance Tradeoffs The synchronous model is fast precisely because it avoids async overhead. Making navigation async introduces significant performance costs even when data is local: - **Microtask overhead**: Every `await` yields to the microtask queue, adding latency between navigation steps. A drill-in that currently takes one synchronous frame would span multiple frames even if all data is cached, creating visible delay or flicker. - **Layout thrashing**: Progressive rendering (show spinner, then replace with content) causes multiple layout passes. The current model lays out tiles once. - **Complexity tax**: Every call site in the navigation chain gains error handling, cancellation logic, and state management. This isn't just code complexity -- it's runtime cost from additional branching, promise allocation, and state tracking on every navigation event. - **Synchronous fast path**: Even with an async architecture, the common case (local data) should remain fast. Maintaining a synchronous fast path alongside async support adds implementation complexity and testing surface. - **Memory pressure**: Caching previously loaded levels for back-navigation trades memory for responsiveness. The cache eviction policy itself becomes a source of bugs and tuning. These tradeoffs are the primary reason this is a long-term direction rather than an immediate goal. The current synchronous model is the right default for most applications -- the performance cost of async navigation is only worth paying when the data genuinely can't be local. ## Relationship to Other Work Several other Future Work items intersect with async navigation: - **[Lazy Slots](../Lazy%20Slots/index.html)** and **[Promise-Wrapped Slots](../Promise-Wrapped%20Slots/index.html)** address async data at the slot level. Async navigation would be the view-layer counterpart -- the container knowing how to wait for and display async subnode sources. - **[Scaling to Large Collections](../Tile%20Container%20Views/index.html)** (in Tile Container Views) describes virtual scrolling and async data sources for tile containers. Async navigation generalizes this from a single container feature to a stack-wide capability. - **[Graph Database](../Graph%20Database/index.html)** would introduce query-based node retrieval, which naturally produces async results. If lazy slots and promise-wrapped slots are implemented first, they may provide the async primitives that async navigation builds on -- the slot system handles the data loading, and the navigation stack handles the display timing. --- Source: /docs/Future%20Work/Complete%20Protocols/ # Complete Protocols Formalizing the many undeclared interface contracts throughout the codebase. ## Context STRVCT has a [protocol system](../../Programming%20Idioms/Protocols/index.html) for declaring interfaces and verifying conformance at class-registration time. In practice, only a handful of protocols have been formally declared (audio clip, audio clip delegate, drag source, drag destination). The rest of the codebase relies on informal duck-typing -- checking for a method's existence before calling it, or simply assuming it exists based on context. This means there are many implicit protocols scattered throughout the framework: sets of methods that classes are expected to implement but that have no formal declaration, no early verification, and no central documentation. ## Examples of Undeclared Protocols These are recurring interface patterns in the codebase that are currently enforced by convention rather than by protocol declarations: ### Node drop protocol Used by tile views to ask a node whether it accepts a dropped item: - `nodeAcceptsDrop(node)` -- returns whether the drop is allowed - `nodeDropped(node)` -- handles the actual drop Currently checked with `if (node && node.nodeAcceptsDrop)` at call sites. A formal `SvNodeDropProtocol` would make the contract explicit. ### Delegate protocols The framework uses a delegation pattern in many places beyond audio. Any class that accepts delegates and calls methods on them defines an implicit protocol. Formalizing these as `SvThingDelegateProtocol` classes would: - Document which callbacks a delegate must implement - Catch missing delegate methods early - Make it easy to find all delegate interfaces in the codebase ### Serialization protocols Classes that participate in persistence implement methods like `recordForStore()`, `loadFromRecord()`, and `referencedBlobHashesSet()`. Classes that participate in JSON exchange implement `serializeToJson()` and `deserializeFromJson()`. These are currently expected by convention -- a formal protocol would verify that a class claiming to be storable actually implements all the required methods. ### View protocols Node views implement a set of methods for synchronization (`syncToNode()`, `syncFromNode()`), lifecycle (`prepareToRetire()`), and display (`postNeedsDisplay()`). These expectations are implicit -- a formal protocol would clarify the minimal interface a view must provide. ## Approach The work is incremental and non-breaking. Each undeclared protocol can be formalized independently: 1. **Identify the contract.** Find all the method names that call sites expect on the target objects. 2. **Create the Protocol subclass.** List those methods as empty declarations in a new `Protocol` subclass. 3. **Register conformance.** Add `addProtocol()` calls to classes that implement the interface. 4. **Verify.** The framework's existing verification machinery checks method presence at registration time. Any missing implementations surface immediately. No existing behavior changes -- the methods already exist on the classes. The protocol declaration simply makes the contract explicit and verifiable. ## Benefits - **Self-documenting interfaces.** Each protocol class is a readable list of required methods, replacing the need to read call sites to discover the contract. - **Early error detection.** A class that claims conformance but is missing a method throws at startup, not when the code path is eventually hit. - **Queryable.** `Protocol.allProtocols()` and `protocol.implementers()` provide runtime introspection of which classes implement which interfaces. - **Inheritance.** Protocols can extend other protocols, building larger contracts from smaller ones without repeating method lists. ## Scope This is an ongoing improvement, not a single task. The codebase has dozens of implicit protocols. Formalizing them should happen organically -- when working in an area of the code, identify the implicit contracts and declare them as protocols. Priority should go to contracts that: - Are implemented by many classes (high fan-out) - Have been a source of bugs from missing methods - Would benefit from being documented as a discrete interface ## Candidates Specific informal protocols identified in the codebase, grouped by priority. Each entry lists a suggested protocol name, the methods it would declare, and where the duck-typing checks currently live. ### High priority These are core framework contracts implemented by many classes. **`SvStorableProtocol`** -- persistence serialization - Methods: `recordForStore(store)`, `loadFromRecord(record, store)` - Duck-typed in: `SvObjectPool.js` (lines 1133, 1453, 1477) - Implementers: `Array_store`, `Map_store`, `ProtoClass_store`, `Object_store`, and 10+ primitive type stores **`SvJsonSerializableProtocol`** -- JSON exchange for AI and data transfer - Methods: `serializeToJson()`, `deserializeFromJson(json)` - Duck-typed in: `SvJsonGroup.js` (lines 429, 445) via `if (value && value.serializeToJson)` - Implementers: `SvJsonGroup`, `SvJsonArrayNode`, `SvJsonDictionaryNode`, `SvField`, `SvBooleanField`, `SvImageNode` **`SvViewSyncProtocol`** -- bidirectional model-view synchronization - Methods: `syncToNode()`, `syncFromNode()` - Duck-typed in: `SvSyncScheduler.js` (scheduled batch calls) - Implementers: `SvNodeView`, `SvStackView`, `SvNavView`, `SvTile`, `SvTilesView`, and 15+ view classes **`SvFieldRenderingProtocol`** -- field display and completion state - Methods: `keyIsComplete()`, `valueIsComplete()`, `keyWhiteSpace()`, `valueCanUserSelect()` - Duck-typed in: `SvFieldTile.js` (lines 397--470) via `if (node && node.keyIsComplete)` - Implementers: `SvField`, `SvAiResponseMessage`, `SvConversationMessage` ### Medium priority Important contracts with more specialized scope. **`SvNodeDropTargetProtocol`** -- node-level drop acceptance - Methods: `nodeAcceptsDrop(node)`, `nodeDropped(node)` - Duck-typed in: `SvTile_dragging.js` (lines 99, 116) - Implementers: `SvLinkNode` **`SvBrowserDropHandlerProtocol`** -- browser drag-and-drop data handling - Methods: `onBrowserDropChunk(chunk)` - Duck-typed in: `SvTilesView.js` (line 469) - Implementers: `SvViewableNode`, `SvJsonArrayNode` **`SvBlobTrackableProtocol`** -- blob reference tracking for garbage collection - Methods: `referencedBlobHashesSet()` - Duck-typed in: `SvObjectPool.js` (line 1846) - Implementers: `SvBlobNode`, `SvField`, `SvPointerField` **`SvNodeVisibilityProtocol`** -- visibility lifecycle notification - Methods: `nodeBecameVisible()` - Duck-typed in: `SvNodeView.js` (line 483) - Implementers: `SvNode`, `SvViewableNode` **`SvNavSelectionProtocol`** -- navigation selection changes - Methods: `didChangeNavSelection()` - Duck-typed in: `SvTile.js` (lines 414, 437) - Implementers: `SvStackView`, `SvTilesView` ### Lower priority Narrower contracts or single-implementer interfaces worth formalizing for documentation. **`SvTileLinkableProtocol`** -- tile navigation target - Methods: `nodeTileLink()` - Duck-typed in: `SvJsonIdNode.js` (lines 185, 232) - Implementers: `SvPointerField`, `SvJsonArrayNode`, `SvOptionsNode`, `SvField`, `SvLinkNode` **`SvCssVariableProviderProtocol`** -- node-defined CSS custom properties - Methods: `cssVariableDict()` - Duck-typed in: `SvNodeView.js` (line 290) **`SvActivatableProtocol`** -- tile activation on keyboard/gesture - Methods: `activate()` - Duck-typed in: `SvTilesView_keyboard.js` (line 185) --- Source: /docs/Future%20Work/Emergent%20Capabilities/ # Emergent Capabilities Capabilities that the naked objects architecture makes possible at the framework level. ## Context In a conventional web framework, cross-cutting concerns -- accessibility, internationalization, search, undo -- must be implemented per component. The cost scales linearly with the number of components, and coverage is always incomplete. STRVCT's architecture inverts this. All state lives in typed slots with rich metadata (type, description, min/max, required, read-only). All UI passes through a small set of view classes (~15 tile and container classes render every application). This means a capability implemented once at the framework level automatically covers every application built on the framework. Two capabilities already demonstrate this pattern: - **[Accessibility](../../Accessibility/index.html)** -- Slot metadata maps to ARIA attributes, view classes emit roles and states, and every application inherits WCAG 2.2 AA support without per-component accessibility code. - **[Internationalization](../../Internationalization/index.html)** -- Translatable nodes and a batched translation service give every application multilingual support through the model layer. The candidates below are additional capabilities that the same architectural properties make possible. Each would be difficult or impractical to add after the fact in a conventional framework, but in STRVCT they can be implemented at the framework level and applied uniformly. ## Candidates ### Undo / Redo Every state change flows through `didUpdateSlot()` with both old and new values. A command history captured at the slot level would give automatic undo/redo to every editable field and action. The notification system already provides the hook points; what's missing is a journal that records slot mutations and can replay them in reverse. The slot system's granularity is an advantage here -- each undo step corresponds to a single slot change with a known previous value, avoiding the complexity of diffing arbitrary state trees. ### Search & Filtering The entire node graph is structured, typed, and labeled. A framework-level search could traverse all nodes and slots, using slot metadata (type, description) to build both full-text and structured queries -- without per-component indexing code. Slot types inform how to search (string matching on text slots, range queries on numeric slots, boolean filtering on flags), and node titles and descriptions provide natural result labels. The same traversal could support filtering a tile container's visible subnodes by query. ### Data Validation Slot annotations already drive three consumers: JSON Schema for AI tool calls, ARIA attributes for screen readers, and persistence constraints. A fourth consumer -- a validation layer -- would use the same metadata (type, min, max, required, pattern) to provide automatic error messages and visual feedback on invalid input. Because the metadata is already there, validation rules wouldn't need to be declared separately from the slot definitions. A field tile with `setAnnotation("minimum", 0)` would automatically reject negative input and display an appropriate message, just as it already emits `aria-valuemin="0"` and `"minimum": 0` in JSON Schema. ### Automated Testing The model is effectively its own specification. Slot metadata declares what types, ranges, and constraints each field accepts. This makes generative testing possible: the framework could automatically produce test cases for every editable slot (valid and invalid input for its type), exercise every action, and walk every navigation path in the node graph. This is a stronger property than conventional snapshot or integration testing -- the tests derive from the model's structural definition rather than from manually written expectations, so they stay in sync as the model evolves. ### Analytics & Telemetry Every user interaction passes through the same tile, navigation, and action paths. Instrumenting once at the framework level would capture usage data (which nodes are visited, which fields edited, which actions taken, how users navigate the hierarchy) for every application -- without per-feature tracking code. The node hierarchy provides natural grouping for analytics events, and slot metadata provides labels and types for structured event properties. ### Debug Inspection STRVCT applications are already their own inspectors -- the node hierarchy is directly navigable, every slot's current value is visible in the UI, and the notification system shows the flow of changes in real time. This isn't a feature to build; it's an inherent property of the naked objects pattern worth calling out as a capability. Conventional frameworks require separate developer tools (React DevTools, Vue DevTools) to inspect component state. In STRVCT, the production UI and the debug inspector are the same thing. ### Migration & Schema Evolution When slot definitions change across versions, the framework knows both the stored schema (from the persisted record) and the current schema (from live slot definitions). This structural knowledge makes automatic migration possible: detecting renamed, added, or removed slots and transforming stored records to match the current definition. In conventional systems, schema migration requires manually written migration scripts. In STRVCT, the slot system provides enough information to detect and potentially automate many common migrations. ## The Common Pattern Each candidate above follows the same structural argument: 1. **Slot metadata already describes the data** -- types, constraints, descriptions, and relationships are declared once in `initPrototypeSlots()`. 2. **A small set of view classes renders everything** -- behavior added to the ~15 tile and container classes covers every application. 3. **The notification system provides hook points** -- `didUpdateSlot()`, `scheduleSyncToView()`, and the notification center make it possible to intercept and react to changes at the framework level. 4. **The node hierarchy provides structure** -- traversal, search, serialization, and inspection all operate on a uniform graph rather than an ad-hoc component tree. The cost of each capability is proportional to the number of view classes (small and fixed), not the number of application components (large and growing). This is the fundamental leverage of the naked objects pattern. --- Source: /docs/Future%20Work/Graph%20Database/ # Graph Database Long-term direction: replacing the current cloud storage backend with a native graph database. ## Context STRVCT's domain model is a cyclic graph of objects connected by typed references. Locally, this graph is stored in IndexedDB as a flat key-value map of serialized records, with object references represented as persistent unique IDs (puuids). Cloud sync currently uses Firebase Storage, with two strategies: per-item JSON files for collections, and whole-pool snapshots with write-ahead log deltas for interconnected object graphs (see [Cloud Object Pools](../../Persistence/Cloud%20Object%20Pools/index.html)). This works, but there's a fundamental mismatch: the domain model is a graph, while the storage and sync layers treat it as either a bag of independent documents (collection sync) or a monolithic blob (pool sync). Neither representation natively understands the structure of the data it's storing. ## Why a Graph Database A graph database would store nodes and edges as first-class entities, directly mirroring how the domain model already works internally: **Structural alignment.** Each domain object becomes a node in the database. Each slot that references another object becomes an edge. The database's structure matches the application's structure — no serialization/deserialization impedance mismatch. **Granular sync.** Changes to a single node or edge can be synced independently without uploading an entire pool snapshot or maintaining a separate delta log. Updating a contact's phone number doesn't require re-uploading the entire address book. **Lazy traversal.** The database can serve subgraphs on demand — fetch a node and its immediate neighbors, then expand as the user navigates. This is the graph-native version of what collection sync's manifest stubs approximate with per-item lazy loading. **Query by structure.** "Find all contacts who share a company with this person" or "show me every group this contact belongs to" become native graph traversals rather than full-table scans or application-level filtering. **Conflict resolution at the edge level.** When two clients modify different parts of the same graph concurrently, a graph-aware system can merge at the node/edge level rather than the document level, reducing false conflicts. ## Prior Art [vertex.js](https://github.com/stevedekorte/vertex.js) is an earlier graph database implementation by the framework author. It stores a persistent graph of nodes with named slots (key-value pairs), where values can be primitives or references to other nodes. The data model is close to what STRVCT's domain objects already look like: - Nodes have unique IDs and named properties - Properties can hold primitive values or references to other nodes - The graph supports cycles and bidirectional traversal - Queries walk the graph by following named edges The concepts from vertex.js — persistent graph nodes with slot-based properties, structural traversal, and path-based addressing — map directly onto STRVCT's existing object model. ## What This Would Replace The current cloud sync stack has several layers that a graph database would subsume: | Current | Graph DB equivalent | |---|---| | JSON serialization of object records | Direct node/edge storage | | puuid-based references between objects | Native graph edges | | Pool snapshots + delta files | Per-node/per-edge mutations | | Manifest-based lazy loading | Subgraph traversal | | Write-ahead log for incremental sync | Change feed on individual nodes/edges | | Lock-based concurrency for pools | Node-level or edge-level conflict resolution | The local IndexedDB layer would likely remain as a client-side cache, but the canonical store would be the graph database rather than Firebase Storage. ## Challenges ### Scalability A hosted graph database must handle concurrent reads and writes from many clients, with low latency for the interactive traversals that drive STRVCT's navigation-based UI. This is a harder operational problem than serving static JSON files from cloud storage. ### Partial sync and offline-first STRVCT applications work offline with local data, syncing when connectivity returns. A graph database sync protocol needs to handle: - Efficient diffing: which nodes/edges changed since last sync? - Subgraph boundaries: which parts of the graph does this client care about? - Conflict resolution: two clients modified the same node offline — how to merge? ### Migration Existing applications store data in Firebase Storage. A migration path is needed — likely a period where both backends coexist, with the graph database gradually taking over as the primary store. ### Hosting and operations Firebase Storage is a managed service with minimal operational overhead. A graph database requires either a managed graph DB service (e.g., Neo4j Aura, Amazon Neptune) or self-hosted infrastructure with its own scaling, backup, and monitoring concerns. ## Object Sub-Pools One concept from the current architecture that should carry forward is the **object sub-pool** — a self-contained subgraph where outside nodes may point to the root, but all internal objects hold no hard references to objects outside the subgraph. These sub-pools can be nested and are the natural unit of ownership, sync, and access control. In practice, sub-pools correspond to the coarse-grained "documents" of an application. In a contacts app, for example, each contact card — with its addresses, phone numbers, notes, and group memberships — forms a sub-pool. The contacts list holds references to each card's root, but the internal objects within a card don't reference objects in other cards. Each sub-pool can be: - **Synced independently** — upload or download a sub-pool without touching the rest of the graph - **Garbage collected** — objects reachable only within the sub-pool can be collected together - **Access controlled** — permissions can be scoped to a sub-pool (e.g., a user owns their contacts but shares specific groups with collaborators) - **Migrated or archived** — move a sub-pool to cold storage or a different backend without breaking references A graph database backend should preserve this sub-pool structure as a first-class concept — not flatten everything into a single global graph. The sub-pool boundary is what makes it practical to sync, permission, and reason about parts of the graph independently, even though the database itself supports arbitrary cross-references. ## Relationship to Current Architecture This is envisioned as a backend replacement, not a framework rewrite. The domain model, slot system, persistence API, and view layer would remain unchanged. The swap would happen below the `SvCloudSyncSource` abstraction: - `SvPersistentObjectPool` continues to manage local IndexedDB storage - `SvCloudSyncSource` (or a new sibling) would speak to a graph database API instead of Firebase Storage - The serialization format would change from JSON documents to node/edge mutations - The sync protocol would change from snapshot+delta to a graph-aware change feed - Object sub-pools remain the unit of sync — the graph database stores the full graph, but sync, permissions, and lifecycle operate at the sub-pool level The domain model already *is* a graph — the change is making the storage layer acknowledge that. ## Timeline This is a long-term architectural direction, not near-term work. The current Firebase-based cloud sync is functional and sufficient for the application's current scale. A graph database backend would become compelling when: - The object graph grows large enough that pool-level sync becomes a bottleneck - Multi-user collaborative editing requires finer-grained conflict resolution than document-level merging - Query patterns emerge that are awkward to express without structural graph traversal --- Source: /docs/Future%20Work/JSON%20Schema/ # JSON Schema Expanding auto-generated JSON Schema coverage across slots, classes, and the domain model. ## Context STRVCT automatically generates JSON Schema from the domain model — classes produce object schemas, slots produce property schemas, and cross-references between classes produce `$ref` and `definitions` entries. These schemas are used for AI tool calls (constraining function parameters), data exchange, and validation. The current implementation covers the most common schema features, but there are gaps at both the slot level (individual property constraints) and the class level (object-wide constraints and composition). Closing these gaps would make the auto-generated schemas more precise, which directly improves AI behavior — tighter schemas reduce hallucinated parameters and improve structured output quality. ## Current Coverage ### Class-level (SvNode) `SvNode.asJsonSchema()` generates object schemas with: | JSON Schema keyword | Source | Notes | |---|---|---| | `type` | Always `"object"` | | | `description` | `jsonSchemaDescription()` | Static method on each class | | `properties` | `jsonSchemaProperties()` | Composed from slots marked `isInJsonSchema` | | `required` | `jsonSchemaRequired()` | From slots marked `isRequired` | | `additionalProperties` | `additionalProperties()` | Boolean — whether extra keys are accepted | | `readOnly` | `jsonSchemaIsReadOnly()` | Optional, class-wide | | `title` | `jsonSchemaTitle()` | Only emitted when different from class name | | `$ref` | `jsonSchemaRef()` | For cross-class references | | `definitions` | `jsonSchemaDefinitionsForRefSet()` | Collects all referenced classes recursively | ### Slot-level (Slot) `Slot.asJsonSchema()` generates property schemas with: | JSON Schema keyword | Slot API | Notes | |---|---|---| | `type` | `setSlotType()` | Mapped to JSON Schema types | | `description` | `setDescription()` | | | `title` | Auto-generated | From slot name | | `default` | `initValue()` | When not `undefined` | | `enum` | `setValidValues()` / `setValidItems()` | | | `examples` | `setExamples()` | | | `pattern` | `setJsonSchemaPattern()` | String regex | | `readOnly` | `setIsReadOnly()` | | | `items` | `setJsonSchemaItemsType()` | Array item type, description, uniqueItems, $ref | | `properties` | `setFinalInitProto()` | Nested object schemas | | `$ref` | Auto-generated | For slots referencing node classes | ## Missing Slot-Level Keywords These JSON Schema keywords have no corresponding slot annotation yet: ### Numeric constraints - **`minimum` / `maximum`** — bounded ranges for Number slots (e.g., ability scores 1–30, hit points 0–999) - **`exclusiveMinimum` / `exclusiveMaximum`** — strict bounds - **`multipleOf`** — step constraints (e.g., currency in increments of 0.01) ### String constraints - **`minLength` / `maxLength`** — character limits for text fields - **`format`** — semantic formats like `"date-time"`, `"email"`, `"uri"`, `"uuid"`. Particularly useful for AI — knowing a string is a UUID vs. a display name changes how the AI generates it ### Array constraints - **`minItems` / `maxItems`** — collection size bounds - **`uniqueItems`** — partially supported on item-typed arrays but not as a general slot annotation ## Missing Class-Level Keywords ### Composition - **`oneOf` / `anyOf` / `allOf`** — union and intersection types. A slot that accepts "either a UoCharacter or a UoCreatureTemplate" currently can't express this. At the class level, this could describe polymorphic collections. - **`const`** — fixed-value properties (more specific than a single-element enum) ### Conditional - **`if` / `then` / `else`** — conditional validation. For example: if `characterClass` is "Wizard" then `spellSlots` is required; if `type` is "ranged" then `range` must be present. These constraints currently live only in code or prompt text. ### Object structure - **`propertyNames`** — constraints on key names for Map-typed slots - **`patternProperties`** — schema for properties matching a regex pattern - **`minProperties` / `maxProperties`** — bounds on the number of properties ## Implementation Approach ### Slot-level additions Each missing keyword follows the existing annotation pattern — a getter/setter pair on `Slot` that `asJsonSchema()` reads: ```javascript // On Slot: setMinimum (n) { this.setAnnotation("minimum", n); return this; } minimum () { return this.getAnnotation("minimum"); } // In asJsonSchema(): const min = this.minimum(); if (min !== undefined) { schema.minimum = min; } ``` The slot system's annotation mechanism already supports arbitrary key-value metadata, so no structural changes are needed — just adding the accessors and wiring them into `asJsonSchema()`. ### Class-level additions Composition keywords (`oneOf`, `anyOf`) would need static methods on `SvNode` that `asJsonSchema()` includes in the class schema. Conditional keywords (`if`/`then`/`else`) are more complex — they'd require a way to express inter-slot dependencies at the class level, possibly as a static `jsonSchemaConditionals()` method that returns an array of condition objects. ### Validation unification Currently, JSON Schema annotations are output-only — they describe the slot's contract to external consumers but don't enforce it at runtime. A natural extension would be to have the auto-generated setter validate against the same constraints: - A `Number` slot with `setMinimum(0)` would reject negative values - A `String` slot with `setMaxLength(100)` would warn on overflow - A `String` slot with `setFormat("email")` could validate format This would unify schema declaration and runtime validation, ensuring the contract described to AI consumers is actually enforced in the application. The existing `validatesOnSet` mechanism already provides the hook point — it just needs to consult the richer set of annotations. ## Richer Type Unions Slots currently declare a single type via `setSlotType("String")`. Some slots legitimately accept multiple types — a value that could be a `String` or `null`, or a slot that holds either a `Number` or an `Array`. The type system doesn't express this, leading to validation warnings that must be suppressed. A possible API: ```javascript slot.setSlotType("String|null"); // or: slot.setSlotTypes(["String", "Number"]); ``` This would generate `oneOf` or union types in JSON Schema and validate against any of the listed types at runtime. ## Open Questions - Should all JSON Schema keywords be supported, or only those that map to runtime behavior? Keywords like `if`/`then`/`else` add schema expressiveness but may be complex to implement as runtime validation. - How should schema versioning work? If the schema changes between app versions, should the schema include a `$id` with a version component? - Could the framework auto-infer some constraints — for example, deriving `minimum: 0` from a slot named "count" or "quantity"? Or is explicit annotation always better? - How should the schema handle slots that are writable by the app but read-only for AI? The current `readOnly` is all-or-nothing. --- Source: /docs/Future%20Work/Lazy%20Slots/ # Lazy Slots Deferring object loading until first access, so large object graphs don't pay for what they don't touch. ## Context STRVCT's persistence system loads every stored object eagerly on startup: each record in IndexedDB is deserialized, its `finalInit()` runs, and all slot values (including referenced child objects) are hydrated immediately. For moderate-sized object graphs this is fine. As the graph grows — hundreds of campaigns, thousands of characters, deeply nested location trees — startup cost grows linearly with the total number of stored objects, regardless of how many the user actually navigates to. Lazy slots would allow a slot to hold a persistent reference (a puuid) rather than the object itself, deferring the actual load to the moment the getter is first called. ## The Idea Mark a slot as lazy: ```javascript { const slot = this.newSlot("locations", null); slot.setIsLazy(true); slot.setShouldStoreSlot(true); } ``` At persistence time, the slot stores the referenced object's puuid instead of the object value. At runtime, the slot's getter recognizes that the value has not been loaded (represented by `undefined`) and fetches the object from the store on demand. ## Existing Scaffolding Partial infrastructure for lazy slots already exists in the `Slot` class: - **`isLazy` flag** — A boolean slot property. When true, `setAllowsUndefinedValue(true)` is set so that `undefined` can represent "not yet loaded" without triggering validation errors. - **`privateNameLazyPid()`** — Returns a private property name (e.g., `_locationsLazyPid`) used to store the puuid alongside the slot value. - **`autoLazyGetter()`** — A generated async getter that checks the lazy pid property and, if present, calls `this.defaultStore().asyncObjectForPid(pid)` to fetch the object. - **`onInstanceSetValueRef()` / `onInstanceGetValueRef()`** — Store and retrieve a reference object in the instance's `lazyRefsMap`, a per-instance Map that tracks which slots have pending lazy references. - **`onInstanceLoadRef()`** — Resolves a stored reference back to an object by calling `storeRef.unref()`, then applies it through the normal setter. - **`copyValueFromInstanceTo()`** — Special-cases lazy slots to copy the lazy pid rather than forcing a load of the value. - **Initialization guard** — During `onInstanceInitSlotValue()`, if `isLazy` is true, the lazy pid is set to `undefined` and an assertion prevents combining `isLazy` with `initProto` (which would defeat the purpose). None of this machinery is currently activated in any class definition — `setIsLazy(true)` is never called outside of commented-out code. ## Challenges to Solve ### Async Getters in a Synchronous World The core tension: `autoLazyGetter()` returns an `async function`, meaning every caller of that getter must `await` it. But the rest of the framework — view synchronization, persistence serialization, slot copying, notification dispatch — assumes getters are synchronous. Sprinkling `await` through the entire call graph is not practical. Possible approaches: - **Eager resolve on navigation.** When a node is about to be displayed, resolve all its lazy slots in a single async pass before handing it to the view system. The getter itself stays synchronous and returns `undefined` until the resolve pass runs. Views already need to handle `null` slot values; `undefined` just adds a "loading" state. - **Synchronous unref from cache.** If the object pool already has the object in its in-memory cache (common after the first access), the lazy getter could return it synchronously. Only the cold-cache path would be async. - **Coroutine-style getters.** JavaScript does not have transparent coroutines, but a future framework version could explore generator-based getters that yield on cache miss and resume when the object is available. This would require framework-level scheduling. ### Persistence Round-Trips When a lazy slot's object is modified and persisted, the lazy slot on the parent still holds only the puuid. The dirty-tracking system needs to understand that a lazy slot's child can be dirty independently of the parent — the parent's record doesn't change just because the child was modified. Conversely, when re-persisting the parent, the system must not force-load a lazy slot just to serialize it. The puuid is sufficient. ### Cascading Lazy Loads If object A has a lazy slot pointing to object B, which itself has a lazy slot pointing to object C, a single user navigation can trigger a chain of async loads. Without care, this creates waterfall latency — each hop waits for the previous one. Options include prefetching (load B and all of B's lazy references in one batch) or breadth-first resolution (resolve one level of the tree at a time). ### UI While Loading Views that display a lazy slot's value will initially see `undefined`. The view layer needs a consistent pattern for: - Showing a loading indicator or placeholder - Re-rendering when the lazy load completes - Handling the case where the load fails (network error, deleted object) This is closely related to the [Async Final Initialization](../Async%20Final%20Initialization/index.html) problem — both require views to handle an "object not yet ready" state. ### Copying and Cloning `copyValueFromInstanceTo()` already handles lazy slots by copying the pid rather than the value. But `duplicate()` — which creates a deep copy — would need to decide: duplicate the pid (creating a shared reference) or force-load the object and duplicate it (preserving deep-copy semantics). The right answer likely depends on the use case. ## Relationship to Async Final Initialization Lazy slots and async final initialization address overlapping problems from different angles: - **Async final init** asks: "What if an object needs async work before it's ready?" - **Lazy slots** ask: "What if we don't load the object at all until someone asks for it?" A lazy slot that resolves its value could trigger the loaded object's `asyncFinalInit` (if that feature existed), creating a clean two-phase deferred initialization. The two features are complementary. ## Open Questions - Should lazy resolution be transparent (the getter handles it) or explicit (callers must call `await node.resolveLazySlot("name")`)? - How does garbage collection interact with lazy slots? A lazy slot holds a puuid but not a strong reference to the object — does the GC need to trace through puuids to find reachable objects? - Could the manifest-based metadata approach (storing title/subtitle in a lightweight manifest) reduce the need for lazy slots by giving stubs enough information to display without loading the full object? - Is there a useful middle ground — "shallow load" — where the object is loaded but its own slots remain lazy? --- Source: /docs/Future%20Work/Promise-Wrapped%20Slots/ # Promise-Wrapped Slots Automatic async getter generation with promise caching and dependency-driven invalidation. ## Context Many slots need to compute their value asynchronously — fetching a URL, querying a database, calling an API. The naive approach is to write a one-off async getter for each such slot, but this leads to repeated boilerplate and easy-to-miss edge cases: what if multiple callers request the value before the first computation finishes? What if a dependency changes and the cached value is stale? Promise-wrapped slots formalize these patterns at the slot level, so that any slot can opt into async computation with built-in promise deduplication and dependency invalidation. ## The Idea Mark a slot as promise-wrapped: ```javascript { const slot = this.newSlot("publicUrl", null); slot.setIsPromiseWrapped(true); slot.setPromiseResetsOnChangeOfSlotName("dataUrl"); } ``` This generates: - **`asyncPublicUrl()`** — an async getter that callers use instead of `publicUrl()`. Returns the cached value if available, or the in-flight promise if a computation is already underway, or kicks off a new computation. - **`asyncComputePublicUrl()`** — a method the class must implement, containing the actual async logic. Called at most once per computation cycle. - **`_publicUrlPromise`** — a private property holding the in-flight `Promise.clone()` instance, ensuring concurrent callers share a single computation. - **`didUpdateSlotDataUrl()`** — an auto-generated hook that resets both the cached value and the promise when the `dataUrl` slot changes, so the next call to `asyncPublicUrl()` recomputes. ## Existing Scaffolding The implementation lives in `Slot_promiseWrapper.js`, a category on `Slot`: - **`isPromiseWrapped`** — boolean flag on `Slot`, registered in `Slot.js` (line 135). When true, `setupPromiseWrapperIfNeeded()` installs the async getter during slot setup. - **`promiseResetsOnChangeOfSlotName`** — optional string naming a dependency slot. When set, `setupPromiseResetMethodIfNeeded()` installs a `didUpdateSlot{Name}` method that nulls both the cached value and the in-flight promise. - **`newAsyncPromiseWrappedGetter()`** — generates the async getter function. The logic: 1. If the private value is non-null, return it immediately (synchronous fast path). 2. If `_slotNamePromise` already exists, return it (promise deduplication). 3. Otherwise, call `asyncCompute{SlotName}()`, set the value via the normal setter on success, and resolve/reject the promise. - **`newPromiseResetMethod()`** — generates the dependency reset function. If the in-flight promise hasn't completed when a reset fires, it rejects the promise with an error before nulling everything. - **Integration point** — `Slot.setupGetter()` calls `setupPromiseWrapperIfNeeded()` after installing the normal getter, so the async getter is available on the prototype alongside the synchronous one. The pattern relies on STRVCT's extended `Promise` class (`Promise_ideal.js`), which adds `callResolveFunc()`, `callRejectFunc()`, `isCompleted()`, and timeout support to native promises via `Promise.clone()`. ## Current Usage - **`SvActorMessage`** — uses `setIsPromiseWrapped(true)` on its `resultPromise` slot so external callers can `await message.asyncResult()` without duplicating the resolution logic. - **`SvImageNode`** — has commented-out promise wrapping on `publicUrl` and related slots, suggesting the pattern was tested there but not yet production-ready. ## Challenges to Solve ### Null as Sentinel The current fast path treats `null` as "not yet computed" — if the slot's value is legitimately `null`, the async getter will recompute on every call. A dedicated sentinel value (e.g., `Slot.UNCOMPUTED`) would be more robust, but adds complexity to the slot value contract. ### Error Recovery When `asyncCompute{Name}()` throws, the promise is rejected and the error propagates to all current awaiters. But the cached promise is not reset — subsequent callers will receive the rejected promise rather than retrying. A retry policy (reset on rejection, optional backoff) would make the pattern more resilient. ### Interaction with View Sync Views that display a promise-wrapped slot's value need to handle three states: no value yet, loading, and loaded. The current view sync system triggers on `didUpdateSlot`, which fires when the setter is called after successful computation — so views update correctly on success. But there's no notification for "computation started" or "computation failed," making loading indicators and error states harder to implement. ### Ordering with Persistence If a promise-wrapped slot is also stored (`setShouldStoreSlot(true)`), the persistence system may serialize `null` (the pre-computation value) to storage. On reload, the slot starts as `null` and recomputes — which may be correct (the value is derived) or wasteful (the value could have been cached). There's no current mechanism to distinguish "not yet computed" from "computed and found to be null" in the stored record. ### Cascading Dependencies `promiseResetsOnChangeOfSlotName` handles one level of dependency. If slot A depends on slot B which depends on slot C, changes to C don't automatically propagate to A. A chain or graph of dependencies would be more general but significantly more complex. ## Relationship to Lazy Slots Promise-wrapped slots and [lazy slots](../Lazy%20Slots/index.html) solve different halves of the async slot problem: - **Lazy slots** defer *loading* an already-persisted object until first access. - **Promise-wrapped slots** defer *computing* a derived value, with caching and invalidation. A slot could potentially be both: lazy-loaded from storage, with a promise wrapper that handles the async load and caches the result. The two mechanisms would need to coordinate on what "not yet loaded" means and who owns the async getter. ## Open Questions - Should the promise wrapper support configurable retry on failure? - Is `null` the right sentinel for "not yet computed," or should a dedicated symbol be used? - Should there be a synchronous `hasComputedPublicUrl()` predicate so views can check without triggering computation? - How should promise-wrapped slots interact with JSON serialization — should derived values be excluded from `serializeToJson()` by default? - Could the dependency system be extended to handle multiple dependencies or dependency chains? --- Source: /docs/Future%20Work/Tile%20Container%20Views/ # Tile Container Views Layout strategies for presenting a node's subnodes. ## Context STRVCT's view architecture has two distinct roles: **tile views** present an individual subnode (its fields, label, appearance), while **tile container views** arrange the array of subnodes spatially and provide uniform interaction (selection, reordering, drill-in, drag-and-drop). Because the framework generates views from model metadata, adding a new container makes it instantly available to any node that opts in -- the same leverage that makes accessibility and i18n automatic. ## Existing Container ### Linear List (`SvTilesView`) The framework's current tile container, which lays out subnodes as a linear array -- vertical (top to bottom) or horizontal (left to right), controlled by the parent `SvStackView`'s direction. This covers most navigation and form-based interfaces. Each subnode is rendered as a tile; the container handles selection, keyboard navigation, reordering, and drill-in. ## Proposed Containers ### Wrapping Grid A container that flows tiles into rows (or columns), wrapping at the container edge. Useful for image galleries, icon grids, card collections, and any set of uniformly-sized items where the count varies. The node would opt in via something like `setNodeTilesViewClass(SvGridTilesView)` and optionally hint at minimum tile width. The grid container handles layout; each cell is still a standard tile -- so selection, drag-to-reorder, and accessibility carry over. ### Spatial Map A pannable, zoomable 2D surface with an optional background image. Subnodes report their positions and are rendered as markers or pins at arbitrary coordinates on the surface. Useful for world maps, dungeon maps, floor plans, relationship diagrams, and any visualization where items exist at continuous positions on a backdrop. The container would support scroll, drag-to-pan, pinch-to-zoom, and mouse-wheel zoom gestures (extending the existing gesture recognizer system). Subnodes would store position in slot annotations (e.g. `setAnnotation("x", 120)`, `setAnnotation("y", 340)`) and the container reads these to place tiles absolutely. Positions could be 2D or potentially 3D (with a projection). The interaction model is navigating *within* a viewport -- the user moves around the map to find things. ### Grid Map A structured grid where each subnode requests a discrete cell position and its tile fills that cell (or spans a rectangle of cells). Useful for tactical battle maps, inventory grids, seating charts, tile-based level editors, and scheduling layouts. Unlike the spatial map, the grid has cell identity -- each position is a discrete slot in the layout. This changes the interaction model: dragging a node between cells is a structured move operation (snapping to grid positions, validating placement rules, swapping or shifting occupants), not a free-form repositioning. Selection, adjacency, and pathfinding queries are natural on a grid but awkward on a continuous surface. The grid may also support cell-level styling (highlighting, coloring regions) and structured keyboard navigation (arrow keys move between cells). While a grid could technically be a special case of a spatial map with snap-to-grid, the interaction patterns diverge enough -- discrete drag-and-drop between cells, cell-based selection and navigation, occupancy rules -- that they likely warrant separate container classes with different gesture handling and different ARIA roles (`grid` + `gridcell` vs. a spatial landmark). ### Table A dense tabular layout where each subnode is a row and its field slots become columns. Field tiles already parse key/value pairs; a table container would render the same data in a compact grid. Column headers would derive from slot names, sortability from slot types. ### Chart A container that renders subnodes as data series in a chart (bar, line, scatter, etc.). The node's slot metadata (type, min, max, description) already provides enough information to auto-generate axis labels and scales -- the same annotations that drive JSON Schema and ARIA attributes. ## Design Considerations - **Opt-in per node**: Nodes select their container via a new `setNodeTilesViewClass()`. The default remains `SvTilesView`. - **Tile reuse**: Each cell, marker, row, or data point should still be a tile (or tile subclass) so that selection, gestures, theming, and accessibility work without reimplementation. - **Stack composability**: New containers should compose within the existing stack navigation model -- drilling into a grid cell or map marker opens the next column just as drilling into a list tile does. - **Responsive behavior**: Containers should adapt to their available size, reflowing or scrolling as needed. The existing `SvNavView` width management and compaction logic would need extension points. - **Accessibility**: Each container needs appropriate ARIA roles (e.g. `grid`, `table`, `img` for canvas) following the same pattern as the `SvNodeView_accessibility` category. - **User-switchable containers**: Nodes could declare a set of supported container views, and the UI would offer a control (e.g. a segmented toggle or menu in the nav header) for the user to switch between them at runtime -- viewing the same subnodes as a list, then as a grid, then as a table. The selected container preference could be persisted per node or per node class. The node would provide the list of appropriate container options (e.g. via `nodeAlternativeTilesViewClasses()`) so the UI only presents choices that make sense for that data -- a collection of images might offer list and grid, while a dataset might offer list, table, and chart. The switcher control would only appear when the node declares more than one option. ## Container Chrome Beyond the tile area itself, containers could support optional header and footer regions with standard affordances: - **Search & filter (header)**: A search field in the container header that filters visible subnodes by matching against node titles, slot values, or other metadata. The container would accept a filter predicate and re-render only matching tiles. For structured data, this could extend to faceted filtering (e.g. filter by slot type or annotation value). - **Create new item (footer)**: A persistent affordance at the bottom of the container for adding a new subnode -- similar to the "+" pattern in list UIs. The container would delegate creation to the parent node, which knows what subnode type to instantiate. This keeps the container generic while letting the node control the semantics. ## Scaling to Large Collections The current `SvTilesView` materializes a tile for every subnode, which works well for typical navigation hierarchies but breaks down for large or asynchronous data sets (thousands to millions of items). A tile container protocol for large collections would need to address: - **Async subnode sources**: Rather than reading subnodes from an in-memory array, the container would request pages or windows of results from an async data source. The node would implement a query interface (filter, sort, offset, limit) and the container would fetch and display subsets on demand. - **Virtual scrolling**: Only tiles visible in the viewport (plus a buffer) would be materialized. As the user scrolls, tiles are recycled and rebound to new subnodes -- the same tile instance represents different data at different scroll positions. - **Search as primary navigation**: When a collection is too large to browse linearly, search becomes the entry point rather than an optional filter. The container could present a search-first UI, displaying results only after the user provides a query, rather than attempting to render all items. - **Automatic hierarchy structuring**: For very wide collections, the container (or the node) could automatically group subnodes into intermediate levels by a property (e.g. alphabetical buckets, category, date range), ensuring no single level presents an overwhelming number of tiles. This would be transparent to the user -- drill into "A-F" to see items in that range -- and could be computed lazily from the data source's sort order. --- Source: /docs/Future%20Work/Tile%20Views/ # Tile Views Tile subclasses for presenting individual subnodes in specialized ways. ## Context STRVCT's view architecture has two distinct roles: **tile container views** arrange the array of subnodes spatially (see [Tile Container Views](../Tile%20Container%20Views/index.html)), while **tile views** present an individual subnode -- its content, appearance, and interaction affordances. Nodes select their tile class via `setNodeTileClass()`. All tiles inherit the standard tile infrastructure -- selection, theming, gestures, accessibility -- and add domain-specific rendering. ## Existing Tiles ### Base Tiles | Class | Extends | Purpose | |---|---|---| | `SvTile` | -- | Base navigable tile. Supports selection, styling, slide-to-delete, reordering, drag, and close/delete buttons. | | `SvTitledTile` | `SvTile` | Adds title, subtitle, note, note icon, and thumbnail subviews. | | `SvHeaderTile` | `SvTitledTile` | Section header with selectable state and specific theme class. | | `SvTextNodeTile` | `SvTile` | Displays and edits text nodes with a `SvTextView`. | | `SvBreadCrumbsTile` | `SvTile` | Breadcrumb path navigation. | ### Field Tiles | Class | Extends | Purpose | |---|---|---| | `SvFieldTile` | `SvTile` | Key/value field with label and value areas. Syncs ARIA from slot metadata. | | `SvStringFieldTile` | `SvFieldTile` | Text input field. | | `SvPasswordFieldTile` | `SvStringFieldTile` | Masked password input. | | `SvBooleanFieldTile` | `SvFieldTile` | Checkbox toggle. | | `SvActionFieldTile` | `SvTile` | Clickable button with action handling. | | `SvTextAreaFieldTile` | `SvFieldTile` | Multiline text input with speech-to-text support. | | `SvPointerFieldTile` | `SvTitledTile` | Navigation link with arrow indicator. | ### Media Input Tiles | Class | Extends | Purpose | |---|---|---| | `SvImageWellFieldTile` | `SvFieldTile` | Drag-and-drop image input. | | `SvVideoWellFieldTile` | `SvFieldTile` | Drag-and-drop video input. | | `SvSceneViewWellFieldTile` | `SvFieldTile` | 3D model input (model/* MIME types). | ### Option Tiles | Class | Extends | Purpose | |---|---|---| | `SvOptionsNodeTile` | `SvTitledTile` | Container for selectable options (`role="listbox"`). | | `SvOptionNodeTile` | `SvTitledTile` | Individual selectable option (`role="option"`). | ### Chat Tiles | Class | Extends | Purpose | |---|---|---| | `SvChatMessageTile` | `SvTextAreaFieldTile` | Chat bubble with HTML support, accessibility, and speech capability. | | `SvChatInputTile` | `SvChatMessageTile` | Multiline chat input. | ### Resource Tiles | Class | Extends | Purpose | |---|---|---| | `SvImageTile` | `SvTitledTile` | Image display with caption derived from node title. | | `SvFontTile` | `SvTitledTile` | Font preview showing family, style, weight, and stretch. | ## Proposed Tiles ### Rich Text Tile A tile whose value area supports formatted text (bold, italic, lists, headings) rather than plain text. Useful for notes, journal entries, descriptions, and any long-form content. Would need to bridge the node's value to a contentEditable region with structured markup. ### Media Tile A standalone playback tile that embeds audio or video transport controls (play/pause, scrubbing, volume). The node would reference a sound or video resource; the tile provides the playback UI. Builds on the existing `SvWaSound` infrastructure for audio and could extend to video via standard HTML5 media elements. The existing `SvVideoWellFieldTile` and `SvSceneViewWellFieldTile` handle drag-and-drop *input* of media files. This proposed tile is the complement: *playback* of media already associated with a node. ### Meter / Progress Tile A tile that renders a numeric slot value as a visual meter, progress bar, or gauge. The slot's `minimum` and `maximum` annotations define the range -- the same annotations that already drive `aria-valuemin` / `aria-valuemax`. Useful for hit points, loading progress, skill levels, and any bounded numeric value. ### Badge / Status Tile A compact tile optimized for small status indicators -- an icon plus a short label, possibly color-coded by state. Useful for tags, status markers, notification counts, and online/offline indicators. Would pair well with the wrapping grid container for dense displays. ### Embed Tile A tile that renders an iframe or sandboxed external content. The node provides a URL; the tile handles sizing, loading states, and security boundaries. Useful for embedding external widgets, previews, or third-party content within the node hierarchy. ## Design Considerations - **Slot-driven rendering**: New tiles should derive their behavior from slot metadata wherever possible (type, annotations, description), keeping domain objects declarative rather than view-aware. - **Tile class selection**: Nodes opt in via `setNodeTileClass(SvImageTile)` etc. The framework's existing view-discovery mechanism handles the rest. - **Accessibility**: Each new tile needs appropriate ARIA roles and state, following the `SvNodeView_accessibility` pattern. For example, a meter tile would use `role="meter"` with `aria-valuemin`, `aria-valuemax`, and `aria-valuenow`. - **Composability with containers**: New tile types should work in any container -- a media tile in a list, an image tile in a wrapping grid, a badge tile in a spatial map. --- Source: /docs/Getting%20Started/ # Getting Started Setup, integration, and first steps with the Strvct framework. ## Prerequisites - Git (with submodule support) - Node.js - [Cursor](https://cursor.sh/) (or VSCode) ## Adding Strvct to Your Project Strvct is designed to be used as a git submodule within your project. From your project's root folder: ``` git submodule add https://github.com/stevedekorte/strvct.net.git strvct ``` If you plan to deploy on GitHub Pages, add a `.nojekyll` file to your root folder to prevent Jekyll processing. ## Project Structure A typical Strvct project looks like this: ``` your-project/ ├── strvct/ # Framework submodule ├── app/ # Your application code │ ├── _imports.json # Declares your app's source files │ └── ... ├── resources/ # Icons, sounds, images ├── build/ # Generated by build (do not edit) │ ├── _index.json # Resource metadata catalog │ └── _cam.json.zip # Compressed content bundle ├── _imports.json # Root imports (references strvct + app) └── index.html # Entry point ``` ## Declaring Imports Strvct uses `_imports.json` files instead of standard ES module imports. Each directory that contains source files needs an `_imports.json` listing its files and subdirectories: ```json [ "_css.css", "external-libs/_imports.json", "source/_imports.json", "app/_imports.json" ] ``` Paths are relative to the directory containing the `_imports.json`. Entries can be direct file paths (e.g. `"MyClass.js"`) or references to sub-imports (e.g. `"subfolder/_imports.json"`). The build system recursively follows all references to discover the complete set of resources and their dependency order. ## Building The build process scans your `_imports.json` tree and produces two files in the `build/` directory: - `_index.json` — metadata catalog with paths, sizes, and SHA-256 content hashes - `_cam.json.zip` — compressed content bundle keyed by hash To run the build manually: ``` node ./strvct/source/boot/index-builder/ImportsIndexer.js ``` For non-code assets (icons, sounds), run the resource indexer: ``` node ./strvct/source/boot/index-builder/SvResourceIndexer.js ./resources/icons ./resources/sounds ``` If your project uses a `justfile`, these are typically wrapped in a `just build` command. ## Running Locally Start the local web server: ``` node ./strvct/webserver/CliWebServer.js --config path/to/config.json --port 8000 ``` The server configuration is a JSON file specifying request handler classes: ```json { "serverName": "MyApp", "requestClasses": [ "./requests/MyRequestHandler.js" ] } ``` Use `--secure true` for HTTPS (you will need to accept the self-signed certificate warning on first visit) or `--secure false` for HTTP. ## Debugging Cursor (and VSCode) can debug Strvct applications via Chrome DevTools. Key points for your launch configuration: - Set `webRoot` to your site directory (the folder containing the `strvct/` submodule) - Map paths with `"pathMapping": { "/": "${webRoot}/" }` - Disable source maps (`"sourceMaps": false`) — Strvct uses `sourceURL` comments instead - Optionally disable network cache (`"disableNetworkCache": true`) during development Example `.vscode/launch.json` configuration: ```json { "name": "local", "type": "chrome", "request": "launch", "url": "https://localhost:8000", "webRoot": "${workspaceFolder}/site", "pathMapping": { "/": "${webRoot}/" }, "sourceMaps": false, "disableNetworkCache": true } ``` This allows breakpoints, stepping, and source display to work correctly with dynamically evaluated code. ## ESLint Strvct includes an ESLint configuration at `strvct/eslint.config.js`. Key rules to be aware of: - **4-space indentation** - **Semicolons required** - **Double quotes** for strings - **Space before function parentheses** — `function ()` not `function()`, `methodName ()` not `methodName()` Install ESLint if you don't have it: ``` npm init @eslint/config -g ``` ## Writing Your First Class Create a file in your app directory and declare it in your `_imports.json`: ```javascript (class MyNode extends SvSummaryNode { initPrototypeSlots () { { const slot = this.newSlot("greeting", "Hello, world!"); slot.setSlotType("String"); slot.setShouldStoreSlot(true); slot.setSyncsToView(true); slot.setCanEditInspection(true); } } initPrototype () { this.setShouldStore(true); } init () { super.init(); return this; } subtitle () { return this.greeting(); } }.initThisClass()); ``` This creates a persistent node with an editable string property that automatically appears in the UI inspector. The framework handles view generation, storage, and synchronization — you only define the model. ## Next Steps - [Technical Overview](../Technical%20Overview/index.html) — High-level architecture and key concepts - [Implementation Overview](../Implementation%20Overview/index.html) — Implementation details for classes, slots, views, and persistence - [Lifecycle](../Lifecycle/index.html) — Boot sequence, node initialization, and persistence cycles --- Source: /docs/Implementation%20Overview/ # Implementation Overview Implementation details for classes, slots, views, and persistence. ## Introduction This document is intended to be read after [Naked Objects](../Naked%20Objects/index.html) and the [Technical Overview](../Technical%20Overview/index.html), which cover the design philosophy and high-level architecture of the Strvct framework. Here we go into the technical details of the implementation and how the various parts work together. Applications are typically composed of **Model**, **UI**, and **Storage** layers. Much of the code and potential bugs in complex real-world applications is the "glue" that synchronizes these layers. Strvct puts enough meta-information in the model layer — through slots and their annotations — to allow the UI and Storage layers, and the synchronization between them, to be handled automatically. You write the model and the rest is handled for you, though custom views can be added when needed. ## Architecture Overview The framework is organized around three layers with a notification-based synchronization system connecting them: - **Model** — A graph of `SvNode` objects. Nodes are the unit of both storage and UI presentation. Model objects hold no references to UI objects — they communicate outward solely by posting notifications, which the other layers observe. - **UI** — Composed of `SvNodeView` subclass instances. Each `SvNodeView` holds a reference to an `SvNode` and observes its change notifications. Multiple views may point to the same node instance. - **Storage** — A `SvPersistentObjectPool` that monitors node mutations, bundles changes within an event loop into atomic transactions, and handles automatic garbage collection of the stored object graph. Only model objects are persisted; UI objects are transient and recreated from the model on load. ### SvApp `SvApp` is the top-level application class that coordinates these layers. It holds two key slots: - **`model`** (`SvModel`) — The root of the persistent model graph. `SvModel` extends `SvStorableNode` and contains the application's entire data structure. It has no dependencies on the UI layer. - **`userInterface`** (`SvUserInterface`) — The root of the UI layer. `SvUserInterface` is an abstract base class with multiple implementations: `SvWebUserInterface` for browsers, `SvCliUserInterface` for command-line use, and `SvHeadlessUserInterface` for running without any UI at all. At startup, `SvApp` opens the persistence store, loads or creates the model, then sets up the user interface. The UI class is selected by name via `userInterfaceClassName`, and applications choose the appropriate implementation based on the runtime environment — typically using `SvPlatform.isBrowserPlatform()` to decide. Because the model layer is completely independent of the UI, the same application code can run headlessly in Node.js for testing, batch processing, or server-side operations. The headless user interface is an empty implementation — the model runs, persists data, and processes logic without any DOM or browser APIs. ## Class System ### Class Definition Classes are defined using ES6 class syntax inside an IIFE and self-register via `.initThisClass()`: ```javascript (class MyClass extends ParentClass { initPrototypeSlots () { // Declare instance properties } initPrototype () { // Configure class-wide settings } init () { super.init(); // Basic instance initialization } finalInit () { super.finalInit(); // Complex initialization, object relationships } }.initThisClass()); ``` `initPrototypeSlots()` and `initPrototype()` should never call `super` — the framework calls them automatically on the entire class hierarchy from base to derived. Other initialization methods (`init()`, `finalInit()`) should call their parent methods with `super`. ### Slots Slots are Strvct's property system. Rather than using raw instance variables, properties are declared as slots in `initPrototypeSlots()`, which auto-generates getter and setter methods and provides metadata for the UI and storage layers. ```javascript initPrototypeSlots () { { const slot = this.newSlot("userName", ""); slot.setSlotType("String"); slot.setShouldStoreSlot(true); // persist to storage slot.setSyncsToView(true); // update views on change slot.setCanEditInspection(true); // allow editing in inspector } { const slot = this.newSlot("settings", null); slot.setFinalInitProto(SettingsNode); // auto-create on init slot.setIsSubnodeField(true); // show as navigable field } } ``` Key slot annotations: - `setSlotType(typeName)` — Expected type, used for type checking and UI generation - `setShouldStoreSlot(true)` — Include this property in persistence records - `setSyncsToView(true)` — Trigger view synchronization when the value changes - `setFinalInitProto(SomeClass)` — Auto-create an instance of this class during `finalInit()` (won't override values loaded from storage) - `setIsSubnode(true)` — Add the slot value to the node's subnodes array - `setIsSubnodeField(true)` — Create a navigable tile for this property in the UI inspector ### Categories Categories extend existing classes with additional functionality from external files, promoting separation of concerns: ```javascript (class SvJsonGroup_patches extends SvJsonGroup { applyJsonPatches (patches) { /* ... */ } }.initThisCategory()); ``` Base classes must be loaded before their categories. Naming convention: `ClassName_categoryName`. ### Protocols Protocols define a set of methods that a class must implement, enabling runtime verification of interface compliance. Declared by creating a subclass of `Protocol` with empty method stubs. Naming convention: `NameProtocol`. ## Coding Conventions Instance properties: - Always begin with an underscore, e.g. `_propertyName` - Should almost never be accessed directly — always use accessor methods - Are never accessed directly by external objects - Getter: `propertyName()`, setter: `setPropertyName(value)` - Use `this.newSlot()` and `this.overrideSlot()` in `initPrototypeSlots()` to define instance properties - Use `this.newClassSlot()` in `initClass()` to define class properties ## Model Layer ### Node Hierarchy The model is a graph of objects inheriting from `SvNode`. Every node has a `subnodes` array (its children) and a `parentNode` reference. Key base classes: - **`SvNode`** — Base node class. Provides subnodes, parent references, notification posting, and slot infrastructure. - **`SvSummaryNode`** — Node with summary generation for UI display (title, subtitle, note). - **`SvStorableNode`** — Node with persistence support. ### Subnodes There are two ways child nodes are used: **Stored subnodes** (`setIsSubnode(true)`) create a permanent parent-child relationship. The child is added to the parent's subnodes array and persisted with it. This is the pattern for collections — classes extending `SvJsonArrayNode` with `shouldStoreSubnodes(true)`. **Subnode fields** (`setIsSubnodeField(true)`) create navigable UI tiles for structured properties. The data remains in the parent's slot; the field provides a navigation point in the inspector. This is the pattern for structured objects — classes with `shouldStoreSubnodes(false)`. ### Fields Fields are nodes that sync to a slot value via their `target` and `valueMethod` slots. They bridge the gap between a node's slot and its UI representation. Examples: `SvStringField`, `SvNumberField`, `SvImageWellField`. ## View Layer ### View Hierarchy The view system is built from a layered class hierarchy where each layer adds a specific capability: `SvElementDomView` → `SvCssDomView` → `SvSubviewsDomView` → `SvListenerDomView` → `SvVisibleDomView` → `SvGesturableDomView` → `SvResponderDomView` → `SvControlDomView` → `SvSelectableDomView` → `SvEditableDomView` → `SvDomView` → `SvFlexDomView` → `SvStyledDomView` → `SvNodeView` Notable layers: - **`SvElementDomView`** — Wraps a DOM element rather than extending it, keeping open the possibility of swapping the DOM as a render layer. - **`SvGesturableDomView`** — Gesture recognizer framework (tap, double-tap, pan, long-press, etc.). - **`SvResponderDomView`** — Focus management and keyboard navigation. - **`SvControlDomView`** — Target/action pattern for connecting views to handler objects. - **`SvStyledDomView`** — Named style states (selected, unselected, active, disabled) with theme class name support. ### SvNodeView `SvNodeView` extends `SvStyledDomView` and is the bridge between model nodes and the DOM. It holds a reference to an `SvNode`, observes its change notifications, and synchronizes the view accordingly. Nearly all application-visible views are `SvNodeView` subclasses. ### Navigation Structure Strvct's UI is based on nested master-detail views using a Miller Column pattern: - **`SvStackView`** — Core navigation unit. Contains a `SvNavView` (master column) and an `otherView` (detail area). Orientation can be left-right or top-bottom. Chains of StackViews automatically compact and expand based on available space. - **`SvBrowserView`** — Top-level `SvStackView` with a breadcrumb path header. - **`SvNavView`** — Navigation column containing a header, a scrollable `SvTilesView`, and a footer. Column width is resizable. - **`SvTilesView`** — Scrollable container for an array of `SvTile` views. Manages selection, cursor navigation, drag-and-drop, and keyboard input. When a user selects a tile, a new `SvStackView` is created in the detail area, with its `SvNavView` populated by the subnodes of the selected node. This recursive structure allows arbitrarily deep navigation. ### SvTile Views Tiles are the individual items displayed in navigation columns: - **`SvTile`** — Base tile with selection, state-based styling, slide-to-delete, long-press reorder, and drag support. - **`SvTitledTile`** — Standard tile with title, subtitle, note, and optional thumbnail. - **`SvHeaderTile`** — Section header tile. - **`SvBreadCrumbsTile`** — Breadcrumb path that auto-compacts to fit available width. ### Field Tiles Field tiles present node properties as key/value pairs in the UI inspector: - **`SvFieldTile`** — Base field tile with key, value, error, and note containers. - **`SvStringFieldTile`** — String property display and editing. - **`SvBooleanFieldTile`** — Boolean property with checkbox/toggle. - **`SvTextAreaFieldTile`** — Multi-line text editing. - **`SvImageWellFieldTile`** — Image display and selection. - **`SvPointerFieldTile`** — Object reference with navigation arrow. - **`SvActionFieldTile`** — Button that invokes an action method on the node. ## Storage Layer ### Record Format The storage system is a key/value store backed by IndexedDB. Keys are persistent unique IDs (puuids) and values are JSON records containing a type field and a payload. On load, the type is used to locate the class, which is then asked to deserialize itself from the payload. Object references within records are stored as puuid strings. This uniform reference format enables the storage system to trace the object graph for automatic garbage collection — only objects reachable from the root node survive collection. ### Persistence Lifecycle Nodes opt into persistence via `setShouldStore(true)`, and individual slots via `setShouldStoreSlot(true)`. The `SvPersistentObjectPool` then: 1. **Monitors mutations** — When a stored slot changes, the owning node is marked dirty via `didMutate()`. 2. **Batches transactions** — All dirty objects are collected at the end of the current event loop and committed atomically. 3. **Handles deserialization** — On load, `instanceFromRecordInStore()` creates a blank instance, `init()` runs, `loadFromRecord()` populates stored values, then `finalInit()` re-establishes object relationships. Slots with `setFinalInitProto()` only create default instances if no value was loaded from storage. 4. **Garbage collects** — Walks the stored object graph from the root; unreachable records are removed. ### Blob Storage Large binary data (images, audio) is stored separately in `SvBlobPool`, a content-addressable store using SHA-256 hashes as keys. Nodes store hash references rather than blob data directly. This separation avoids blocking the synchronous object pool API with large async I/O operations, and provides automatic deduplication. ## Synchronization Strvct uses a notification-based system to keep layers synchronized without tight coupling. The key components are: - **`SvNotificationCenter`** — Deferred, deduplicated event dispatch. Nodes post named notifications; observers register to receive them. - **`SvSyncScheduler`** — Coalesces method calls so that multiple changes in one event loop result in a single sync pass. - **`SvBroadcaster`** — Lightweight immediate broadcast for high-frequency internal events. Observations use weak references, so garbage collection of either party automatically cleans up the subscription. For full details on posting, observing, scheduling, and weak reference cleanup, see the [Notifications](../Notifications/index.html) guide. For sync loop detection, see the [Views](../Views/index.html) guide. ## Build System ### Overview Strvct does not use standard ES module imports or bundlers like Webpack or Rollup. Instead, it uses a custom Content-Addressable Memory (CAM) build and loading system designed around two goals: minimal network transfers and fine-grained caching. ### Motivation Standard ES module imports issue a separate HTTP request per file, which becomes a significant bottleneck for large applications with hundreds of source files. Bundlers address this but sacrifice fine-grained caching — changing one file invalidates the entire bundle. Strvct's CAM system provides the compression benefits of bundling while preserving per-file cache granularity through content hashing. ### Build Process The build process runs two indexers: - **ImportsIndexer** scans `_imports.json` files throughout the source tree to discover all JavaScript and CSS resources and their dependency order. It produces two output files: - `_index.json` — a metadata catalog listing every resource path and its SHA-256 content hash - `_cam.json.zip` — a compressed bundle containing the actual file contents, keyed by hash - **SvResourceIndexer** scans specified directories (e.g. icons, sounds) and produces a similar index for non-code assets. ### Runtime Loading At runtime, the client-side `SvResourceManager` loads the small `_index.json` first, then scans CAM-eligible hashes (js, css, svg, json, txt) against its local `SvHashCache` (an IndexedDB-backed store). If the ratio of missing bytes exceeds a threshold (default 30%), it downloads the full `_cam.json.zip` bundle; otherwise, individual cache misses are loaded on demand. The bundle is unpacked into the client-side IndexedDB database, keyed by content hash. On subsequent loads, resources are served directly from this local store. Because content is addressed by hash, identical content across different file paths is stored only once, and unchanged files are never re-downloaded — even across deployments. Stale cache entries are garbage-collected against the current index after every load. | Scenario | Missing Ratio | Action | |---|---|---| | Fresh install (empty cache) | 100% | Loads full CAM bundle | | No changes (fully cached) | 0% | Skips CAM, serves from cache | | Small update (few files changed) | ~5% | Skips CAM, loads individual files on demand | | Major update (many files changed) | ~60% | Loads full CAM bundle | Resources are loaded in dependency order as declared in `_imports.json` files. CSS is evaluated sequentially to preserve cascade ordering. JavaScript files are evaluated via `eval()` with `sourceURL` comments to enable full debugger support (breakpoints, stepping, source display in DevTools). --- Source: /docs/Inspirations/ # Inspirations Frameworks and ideas that shaped STRVCT's design. ## Miller Columns The default navigation pattern — a horizontal stack of nested tile lists — draws from Miller columns, created by Mark S. Miller in 1980 and closely related to the class hierarchy browser in Smalltalk-76. The pattern appeared in the Macintosh Finder, the NeXTSTEP File Viewer, and later macOS's column view. The NeXTSTEP implementation was the primary inspiration — its combination of columnar navigation with an object-oriented framework made the pattern feel natural for browsing object hierarchies, not just file systems. This provides spatial context as users navigate deeper into a node hierarchy, with each level visible alongside its parent. STRVCT extends the classic Miller column pattern by allowing each level in the stack to independently choose its layout direction — vertical or horizontal — controlled by the node via `setNodeIsVertical()`. This recursive variant means a horizontal stack can contain a column whose subnodes lay out vertically, which in turn contains a row whose subnodes lay out horizontally, and so on. The alternating directions let the same navigation model serve both hierarchical browsing (vertical lists) and detail views (horizontal field layouts) without switching interaction paradigms. ## Smalltalk, Self, and Objective-C STRVCT's object model draws from the Smalltalk tradition. Naming follows Smalltalk conventions — camelCase with `_ivar` / `ivar()` / `setIvar()` accessors. Objects are created via `clone()` rather than constructors, reflecting Self's prototype-based model. The category system, which extends classes without modifying their source files, comes directly from Objective-C (and before that, Smalltalk). The slot system for declaring instance variables echoes Smalltalk's explicit ivar declarations, and the notification center pattern traces back to Smalltalk's change/update mechanism. ## Cocoa and UIKit The application framework layer is heavily influenced by Apple's Cocoa and UIKit: - **Notification center** — `SvNotificationCenter` with observations mirrors `NSNotificationCenter` closely, providing publish/subscribe communication between loosely coupled components. - **Responder chain** — `SvResponderDomView` implements `becomeFirstResponder()`, `releaseFirstResponder()`, `acceptsFirstResponder`, and `nextKeyView`, mapping directly to Cocoa's `NSResponder` / UIKit's `UIResponder` model for keyboard focus and event routing. - **Gesture recognizers** — `SvTapGestureRecognizer`, `SvPanGestureRecognizer`, `SvPinchGestureRecognizer`, and `SvLongPressGestureRecognizer` follow the UIKit `UIGestureRecognizer` pattern, including centralized arbitration of competing gestures via `SvGestureManager`. - **Delegate pattern** — Used throughout for communication between views, listeners, and gesture recognizers, following Cocoa's preference for delegation over subclassing. --- Source: /docs/Internationalization/ # Internationalization AI-powered translation of UI text with persistent caching, batched requests, and smart content filtering. ## Overview STRVCT includes a built-in internationalization (i18n) system that translates UI strings on the fly using AI language models. Rather than maintaining static translation files for each language, the framework sends batched translation requests to an AI service and caches the results persistently in IndexedDB. This means any application built on STRVCT can support new languages without manual translation effort. This approach is uniquely enabled by the naked objects pattern. Because the framework generates the UI from model slot annotations, there is a single point — the model-to-view boundary — where all display text passes through. Translation is injected at that boundary, making every model class translatable by default with no per-component wiring. In a bespoke-view framework, every component would need explicit `t()` calls and separate translation key files. The system is designed around three principles: - **Non-blocking** — translations happen asynchronously. The UI displays English text immediately and swaps in the translated version when it arrives, with no visible delay on subsequent visits thanks to persistent caching. - **Smart filtering** — numbers, currency amounts, codes, URLs, emails, and other non-linguistic content are detected and skipped automatically, avoiding unnecessary API calls. - **Batched and deduplicated** — individual translation requests are debounced, grouped by context, and sent as a single batch to minimize API usage. ## Implementation Overview Translation storage is fully decoupled from the STRVCT SvObjectPool. Each language gets its own pair of IndexedDB databases — one for cache, one for store — so loading never needs to scan or filter entries from other languages. **SvI18n** (singleton coordinator) - Coordinates cache and store; each owns its own per-language IndexedDB - On language change, opens new databases on both cache and store - Inactive when language is English - Computed status getters (`queuedCount`, `completedCount`) for admin UI **SvI18nCache** (eager, in-memory, for common UI text) - Owns a per-language IndexedDB at `i18nTranslations/cache/{lang}` - Loads all entries from its database into a `Map` on language select (no filtering needed) - Seed entries loaded from cloud seed file - Runtime additions (e.g. short common phrases) also stored here - High water mark = max(highWaterMarkDefault, 3× seed count). FIFO eviction on timestamped entries only - Writes runtime additions back to IndexedDB - Synchronous lookups - Handles legacy format migration (plain strings from Phase 1) **SvI18nStore** (async IndexedDB bridge) - Owns a per-language IndexedDB at `i18nTranslations/store/{lang}` - No in-memory state — purely an async read/write interface to IndexedDB - Async lookups; results promoted to SvI18nCache by SvI18n - Fire-and-forget persistence for new translations **SvTranslatableNode** (per-node translation map) - Each node maintains a lazy `translationMap` (string→string, FIFO capped at 10) - Auto-clears when it detects the language has changed (no notification observers needed) - Lives on the node (not the view) so translations survive view reallocation during UI navigation - Provides tier-0 sync lookup before hitting the shared cache NOTE: If accumulated node translation maps become a memory concern, a GC sweep could be added: walk all views, collect their referenced nodes, and clear translationMaps on all in-memory nodes not in that set. **Value format in IndexedDB (serialized as JSON):** - Seed entry: `{ t: "translation" }` (no timestamp = permanent, never evicted) - Runtime entry: `{ t: "translation", ts: 1711382400000 }` (evictable by FIFO) **IndexedDB key:** source English text (language is encoded in the database path, not the key) **Lookup order:** 1. Node's translationMap (sync) — per-node FIFO cache of recent lookups 2. SvI18nCache Map (sync) — all entries for current language 3. SvI18nStore IndexedDB (async) — promotes hit to cache + node map 4. Queue for AI translation — stores result in cache + store; node map updated on promise resolve **Seed update flow:** 1. Check cloud seed version vs local version 2. If newer: clear ALL IndexedDB entries for that language (losing runtime additions) 3. Write new seed entries (no timestamps) to IndexedDB 4. Load into cache Map 5. Runtime additions rebuild organically A new seed may imply context or setting changes, so stale runtime translations should not persist. ## Architecture The i18n system consists of five classes that form a pipeline from UI request to cached translation: | Class | Role | |-------|------| | `SvI18n` | Singleton coordinator. Owns the dedicated IndexedDB, manages language state, delegates to cache/store/service, deduplicates pending promises. Computed status getters for admin UI. | | `SvI18nCache` | Eager in-memory cache. Loads all entries for the current language from IndexedDB into a `Map`. Handles seed entries and runtime additions with FIFO eviction. | | `SvI18nStore` | Async IndexedDB bridge. Provides on-demand async lookups and fire-and-forget persistence. No in-memory state. | | `SvI18nService` | Batched request service. Debounces incoming requests, groups them by context, sends them to an AI chat model, and stores results. | | `SvTranslationFilter` | Pluggable filter with named functions in a Map. Determines whether a string contains translatable linguistic content. Applications can add domain-specific filters. | Supporting integration points: | Class | Role | |-------|------| | `SvTranslatableNode` | Base class for translatable nodes. Maintains a per-node `translationMap` for flicker-free re-renders. Sits between `SvTitledNode` and `SvInspectableNode`. | | `Slot` | Supports `translationContext` (per-slot context) and `shouldTranslate` (disable translation) annotations. | | `SvFieldTile` | Calls `translatedValueOfSlotNamed()` to display translated keys and placeholder text. Skips translation for editable keys. | | `SvOptionNode` | Respects the parent slot's `shouldTranslate` annotation for option labels. | | `SvBreadCrumbsTile` | Observes `"svI18nLanguageChanged"` to rebuild breadcrumbs in the new language. | | `SvServices` | Hosts the `SvI18n` singleton as a subnode field. | All i18n source files live in `source/library/i18n/`. ## Translation Flow When a view needs to display a slot value: 1. **Request** — `SvFieldTile` calls `node.translatedValueOfSlotNamed("slotName")` on the model node. 2. **Filter** — `SvTranslatableNode` checks whether translation is needed: - Is translation enabled and the language not English? - Is the text short enough (20 words or fewer for slot values)? - Does `SvTranslationFilter` confirm the string contains translatable text (not a number, URL, email, etc.)? 3. **Node map lookup** — The node's `translationMap` is checked first (sync, O(1)). This is a string→string Map capped at 10 entries with FIFO eviction. It auto-clears if the language has changed since last access. On hit, returns immediately with no flicker. 4. **Cache lookup** — `SvI18n.cachedTranslate(text, context)` checks the cache Map. On hit, the result is stored in the node's translationMap and returned. 5. **Async lookup** — On sync miss, `SvI18n.asyncTranslate(text, context)` is called. This returns a Promise and checks the store's IndexedDB layer. If found, the value is promoted to the cache, stored in the node's translationMap, and the promise resolves. 6. **AI translation** — If not found in any layer, the text is enqueued in `SvI18nService`: - Deduplicates: if the same key is already pending, the new caller's promise chains onto the existing request. - Returns the English text as an immediate fallback. 7. **Batch** — `SvI18nService` accumulates requests for 200ms (configurable), then groups them by context and sends one AI request per group. Each request includes a JSON template mapping English strings to empty values, asking the model to fill in translations. 8. **Response** — The AI returns a JSON object mapping English to translated strings. The service stores each result in both the cache (for sync access) and the store (IndexedDB persistence), then resolves all pending promises for those keys. 9. **View update** — The resolved promise stores the result in the node's translationMap and calls `didUpdateNode()`, which triggers a view sync. The view calls `translatedValueOfSlotNamed()` again, this time getting a sync hit from the node map, and displays the translated text with no flicker. ## SvTranslationFilter The filter uses a pluggable Map of named filter functions. Each function takes a trimmed string and returns `true` if it should NOT be translated. Names prevent duplicate registration. Built-in filters: | Filter | Examples | |--------|----------| | No alphabetical characters | `17 (+3)`, `+5 / +3 / +2`, `---` | | Numeric and currency | `42`, `$9.99`, `€100`, `$70.16 USD`, `100%` | | Alphanumeric codes | `23A`, `#7`, `3d6`, `+5`, `10/20` | | Ordinals | `1st`, `2nd`, `3rd` | | Labeled numeric | `HP: 45`, `AC: 18`, `XP: 1,200` | | Email addresses | `user@example.com` | | Phone numbers | `+1 (555) 123-4567` | | URLs | `https://example.com`, `www.example.com/path` | | IP addresses | `192.168.1.1`, `::1`, `2001:db8::1` | Applications can add domain-specific filters: ```javascript SvTranslationFilter.shared().addFilter("diceNotation", s => /^\d*d\d+(\s*[+-]\s*\d+)?$/i.test(s)); ``` The main entry point is `SvTranslationFilter.shared().shouldTranslate(value)`, which returns `false` for any matching filter. ## Caching Translation caching uses two tiers backed by a single dedicated IndexedDB, separate from the main STRVCT SvObjectPool. ### Tier 0: Node Translation Map (Per-Node, Synchronous) Each `SvTranslatableNode` maintains a lazy `translationMap` — a plain `Map` from English source text to translated text. This provides the fastest possible sync lookup and prevents flicker on view re-render. Key properties: - **FIFO capped at 10** — enough for title, subtitle, and a handful of labels per node - **Lazy allocation** — the Map is only created when the first translation is stored (English-only users pay zero cost) - **Auto-clears on language change** — tracks the language it was populated for and clears itself if the language differs on next access (no notification observers needed) - **Lives on the node, not the view** — survives view reallocation during UI navigation (clicking between tiles in a SvTilesView) ### Tier 1: SvI18nCache (Eager, Synchronous) The cache loads all entries for the current language from IndexedDB into a `Map` when the language is set. This provides O(1) synchronous lookups for the majority of UI strings, ensuring no flash of untranslated text. Entries come from two sources: - **Seed entries** — loaded from a pre-generated JSON file. These have no timestamp and are permanent (never evicted). - **Runtime additions** — short common phrases that accumulate during use. These have a timestamp and are evictable by FIFO. The high water mark for eviction is 3x the seed entry count (minimum 100). When the total entry count exceeds this, the oldest timestamped entries are evicted from the Map (they remain in IndexedDB for the store to access). ```javascript // Synchronous lookup (called by SvI18n.cachedTranslate) cache.lookup("Hit Points", "es"); // → "Puntos de Golpe" or null // Store a runtime addition cache.store("New Phrase", "es", "Nueva Frase"); // persists to IndexedDB too ``` ### Tier 2: SvI18nStore (Async, IndexedDB) The store is a thin async bridge to IndexedDB for translations not in the eager cache. It has no in-memory state — lookups go directly to IndexedDB, and results are promoted to the cache by SvI18n for future sync access. ```javascript // Async lookup (IndexedDB only) const translation = await store.asyncLookup("some text", "es"); // Fire-and-forget persist to IndexedDB store.storeSync("some text", "es", "algún texto"); ``` ### Seed Files Seed files provide instant translations on cold start. The format groups entries by context: ```javascript { meta: { language: "es", entryCount: 150 }, entries: { "ui-label": { "Hit Points": "Puntos de Golpe", ... }, "game-mechanic": { "Saving Throw": "Tirada de Salvacion", ... } } } ``` When a seed file is loaded, its entries are written to the cache Map (for sync access) and to IndexedDB (for persistence). Seed entries have no timestamp, making them permanent — they survive eviction. When a new seed version is detected, all entries for that language are wiped from IndexedDB and replaced with the new seed. Runtime additions are lost and rebuild organically through use. ### Seed Generation Seed files are generated through a two-phase process accessible via the admin UI (Services > Internationalization, in debug mode): **Phase 1 — Introspection (Init Seed button)** 1. Clears the cache for the current language 2. Walks all `SvNode` subclass prototypes via STRVCT introspection 3. For each slot with `shouldTranslate() === true`, collects: labels, placeholder text, valid value labels, and string defaults 4. Filters collected strings through `SvTranslationFilter` 5. Queues all collected strings through the normal translation pipeline (batched AI via SvI18nService) **Phase 2 — Runtime Accumulation** - Browse the application UI to trigger translations of dynamic strings not discoverable via introspection (computed titles, subtitles, action names, etc.) - The admin UI shows live queued/completed counts to track progress **Phase 3 — Store (Store Current Seed button)** 1. Exports all cache entries for the current language as a seed JSON file 2. Strips all timestamps (treating everything as permanent seed entries) 3. Uploads to Firebase Storage, replacing the existing seed file for that language **IMPORTANT:** Seed generation must be done in production to save to the correct cloud storage location. Running in a dev environment would save to the dev project's storage, not the production seed files that users download. ## Making Nodes Translatable Nodes opt into translation by extending `SvTranslatableNode` (which sits between `SvTitledNode` and `SvInspectableNode` in the hierarchy). This provides three methods: - **`translatedValueOfSlotNamed(slotName)`** — Returns the translated value of a slot, falling back to English while a translation is pending. - **`translatedValuePlaceholderOfSlotNamed(slotName)`** — Translates a slot's placeholder text annotation. - **`translationContext()`** — Override to provide a default context for the node's slots (default: `"ui-label"`). Individual slots can declare their own context via the `translationContext` annotation: ```javascript initPrototypeSlots () { { const slot = this.newSlot("hitPoints", 0); slot.setSlotType("Number"); slot.setTranslationContext("game-mechanic"); } } ``` The context string is sent to the AI with the translation request, helping it choose appropriate terminology (e.g., "game mechanic" vs. "ui label" vs. "dnd-character-sheet"). ## Configuration ### Language ```javascript // Set the target language (ISO 639-1 code) SvI18n.shared().setCurrentLanguage("es"); // Check if translation is active SvI18n.shared().needsTranslation(); // true when enabled and language != "en" // Enable or disable the system SvI18n.shared().setIsEnabled(true); ``` Setting the language posts an `"svI18nLanguageChanged"` notification that views can observe to trigger a full UI refresh. Internally, the cache reloads from IndexedDB for the new language. Node translation maps auto-clear lazily on next access when they detect the language has changed. ### Service Tuning `SvI18nService` has configurable parameters: | Parameter | Default | Purpose | |-----------|---------|---------| | `debounceMs` | 200 | Milliseconds to wait before sending a batch | | `maxBatchSize` | 50 | Maximum strings per API request | | `maxConcurrent` | 3 | Maximum simultaneous API requests | | `chatModel` | (lazy) | AI model to use; defaults to `SvServices.shared().defaultChatModel()` | ### Access The `SvI18n` singleton is hosted on `SvServices`: ```javascript const i18n = SvServices.shared().i18n(); ``` ## View Integration ### Field Tiles `SvFieldTile` calls `node.translatedValueOfSlotNamed()` in its `visibleKey()` method, so all field tiles automatically display translated labels when the i18n system is active. Placeholder text on input fields is translated via `translatedValuePlaceholderOfSlotNamed()`. ### Breadcrumbs `SvBreadCrumbsTile` observes the `"svI18nLanguageChanged"` notification. When the language changes, it clears its cached path and rebuilds all breadcrumb segments with translated titles. ### Custom Views Any view can observe language changes and refresh: ```javascript init () { super.init(); SvNotificationCenter.shared().newObservation() .setName("svI18nLanguageChanged") .setObserver(this) .startWatching(); return this; } svI18nLanguageChanged () { this.scheduleSyncFromNode(); } ``` ## Translation Safety Translation must never corrupt model data. The system translates values at the view boundary for display only — translated strings must never flow back into the model, serialization, or game state. **Serialization boundary** — All JSON serialization paths (`asJson()`, `serializeToJson()`, `getClientState()`) read raw slot values, not translated display values. AI services, game mechanics, and network sync never see translations. **Editable keys** — When `keyIsEditable()` is true on a field tile, `visibleKey()` returns the raw slot value without translation. This prevents translated text from being written back to the model when the user edits the key. **`shouldTranslate` slot annotation** — Setting `slot.setShouldTranslate(false)` disables translation for a slot's value. `SvTranslatableNode.translatedValueOfSlotNamed()` checks this annotation and returns the raw value when false. `SvOptionNode` also checks the parent slot's `shouldTranslate` annotation, so option labels inherit the setting — for example, a language picker's options stay in their native script rather than being translated. ```javascript { const slot = this.newSlot("language", "en"); slot.setShouldTranslate(false); // "Espanol", "Francais" stay untranslated slot.setValidItems([ { label: "English", value: "en" }, { label: "Espanol", value: "es" } ]); } ``` ## Framework / App Boundary The i18n system is split between the STRVCT framework and the application layer: **Framework responsibility** — caching, persistence, view hooks, batched AI requests, filtering, and notification dispatch. The framework is domain-agnostic — it has no knowledge of what the application's content is about. **App responsibility** — configuration of which AI model to use, what system prompt to send (providing domain-specific context like "use official D&D terminology"), language picker UI, seed file management, domain-specific translation filters, and any domain-specific translation overrides. Any STRVCT-based application can reuse the i18n system by providing its own system prompt and model choice via `SvI18nService` configuration. ## Layout Considerations Different languages have different text metrics that affect UI layout: - **Text expansion** — German and French text is typically 20-40% longer than English. Japanese and Korean can be shorter. Avoid fixed-width containers for translated text. - **CJK line breaking** — Japanese and Korean text can break between any character (no word spaces). Browsers handle this natively. - **Right-to-left (RTL)** — Arabic and Hebrew require CSS `direction` and logical properties (`margin-inline-start` vs `margin-left`). STRVCT's flexbox-based layout should adapt, but RTL is not yet supported. - **Number and date formatting** — Use `Intl.NumberFormat` and `Intl.DateTimeFormat` for locale-aware formatting. These are built into the browser and do not require AI translation. ## Design Notes **Promise deduplication** — If multiple views request the same translation simultaneously (common during initial render), only one API request is made. All callers' promises are chained to the same pending entry. **Lazy model resolution** — `SvI18nService` lazily loads the chat model from `SvServices` on first use, avoiding circular dependency issues during framework initialization. **Word limit** — Strings longer than 20 words are skipped by default, on the assumption that long text is content rather than UI labels. Placeholder text is exempt from this limit. **Context grouping** — Batches are grouped by translation context before sending, so the AI model receives semantically related strings together, improving translation consistency. **SvObjectPool decoupling** — Translation storage is fully separate from the STRVCT SvObjectPool. This avoids loading thousands of translation records into the main persistence layer, keeps translation I/O independent of model persistence, and simplifies the data format (plain `{ t, ts }` objects instead of full `SvStorableNode` instances). **Sync loop safety** — Status display slots (`queuedCount`, `completedCount`) use computed getter overrides rather than `setSyncsToView`. This avoids sync loops: since translation lookups happen during view sync, any state mutation with `syncsToView` during that cycle would create infinite feedback. The computed getters read live state without mutation. **Node vs view translation maps** — Translation maps live on the node, not the view, because views are ephemeral (reallocated when navigating between tiles in a SvTilesView) while nodes persist. A view-level map would be empty every time the user navigates back to a previously visited tile. ## Comparison to Other Frameworks Most web frameworks treat i18n as an opt-in layer that developers wire into each component. STRVCT takes a fundamentally different approach by making translation an emergent property of the naked objects pattern. ### Traditional Approach (React-intl, vue-i18n, next-intl, Angular i18n) - Developers maintain **separate JSON translation files** for each language, with developer-defined keys like `"character.hitPoints"`. - Every visible string must be **explicitly wrapped** with a `t()` call or `` component. - All translations are loaded at startup or per-route — lookup is always synchronous. - Adding a new language requires producing a complete translation file and ensuring every string in every component is wrapped. - The localization cost grows linearly with the number of components and strings. ### STRVCT's Approach - **No translation files to maintain.** Translations are generated by AI and cached persistently in IndexedDB. - **No per-component wiring.** Because the naked objects pattern means all UI text flows from model slots through a single framework-controlled rendering pipeline, translation is injected at the model-to-view boundary automatically. New model classes are translatable by default. - **Adding a new language is a configuration change**, not a translation project. - **Progressive rendering.** English text displays immediately; translated text swaps in asynchronously when it arrives from cache or AI. No blocking on translation load. - **Domain-aware context.** Slot annotations provide translation context (e.g., "game mechanic" vs. "ui label") that travels with the model, improving translation quality without developer effort. - **Optional seed files for instant cold starts.** A seed file per language can be pre-generated via introspection of class prototypes and runtime accumulation, then stored in cloud storage for all users to download. ### Tradeoffs | | Traditional i18n | STRVCT i18n | |---|---|---| | Translation source | Human translators or external services | AI-generated with persistent caching | | Developer effort per string | Wrap with `t()`, add key to JSON file | None (automatic) | | New language cost | Full translation file + QA | Configuration change | | Determinism | Same input always produces same output | Cached after first translation | | Professional quality | Human translators catch cultural nuance | AI may miss subtle distinctions | | Runtime cost | None (static lookup) | Async on first encounter, then cached | | Tooling maturity | Extraction tools, TMS platforms, CI checks | Slot annotations, automatic seed generation from model | Traditional i18n excels when professional translation quality is critical and a mature localization workflow is already in place. STRVCT's approach excels when minimizing developer effort is the priority and the application needs to support new languages rapidly without a dedicated localization team. ## Planned Enhancements ### Shared Cloud Seed A shared cloud seed so all users benefit from translations: - A single `seed.json` per language hosted in cloud storage (e.g., Firebase Storage at `public/translations/{languageCode}/seed.json`). - Admin-write, everyone-read access rules. - On cold start, clients check the seed's metadata (ETag/timestamp) against a locally cached version to avoid unnecessary downloads. - A promotion Cloud Function moves validated translations from local caches to the shared seed. ### Translation Persistence Annotation A `translationPersistence` slot annotation would control where translations are stored: - **`"local"`** — Cached on-device only. For transient content that is unlikely to repeat. - **`"cloud"`** — Promoted to the shared pool. For standard UI labels and reusable content that many users in the same language will see. ### Content Page Translation An `` tag for content pages would mark sections as safe to translate while leaving surrounding content (license text, code blocks, URLs) untouched. Each tagged section would be translated as a unit to preserve paragraph flow and tone. ### View-Layer String Translation Some UI text is hardcoded in view classes rather than flowing through the node slot system (e.g., tooltip text, placeholder text set via CSS `data-placeholder`). These strings need a separate translation path, potentially via a direct `SvI18n.translate()` call in the view. --- Source: /docs/Lifecycle/ # Lifecycle Boot sequence, node initialization, view synchronization, and persistence cycles. The core design challenge is that the same object must work whether it is freshly created or loaded from storage. A single-phase constructor can't handle both cases — if it creates default child objects, it will overwrite data that was just deserialized. STRVCT solves this with a three-phase sequence: `init()` sets up primitives and slot defaults, `finalInit()` creates complex child objects only if they weren't already loaded from the store, and `afterInit()` runs once the full object graph is ready. This separation means persistence is transparent — a class doesn't need different code paths for "new" vs. "loaded." - Boot Sequence - App Lifecycle - Node Lifecycle - View Synchronization - Persistence Lifecycle - Headless Execution --- Source: /docs/Lifecycle/App%20Lifecycle/ # App Lifecycle SvApp initialization, extension points, and readiness signaling. ## Overview `SvApp` is the application singleton. It owns the persistent store, the root model object, and the top-level UI. Subclasses override a small number of hook methods to set up their model and UI without needing to manage the initialization order themselves. ## Extension Points | Method | When it runs | What to do here | |--------|-------------|-----------------| | `setupModel()` | Store is open, root object exists | Initialize your data model, add top-level nodes | | `setupUi()` | Model is ready | Customize the UI setup | | `appDidInit()` | Everything is wired up | Post-init work, the app is now live | | `afterAppUiDidInit()` | UI is visible | Handle URL parameters, trigger initial navigation | | `handleSearchParams(params)` | Called from `afterAppUiDidInit` | Process URL query parameters | Use `rootSubnodeWithTitleForProto(title, proto)` to add top-level objects — it creates them on first run and reuses them from storage on subsequent loads. ## Waiting for Readiness The app exposes a promise for code that needs to wait: ```javascript await SvApp.shared().didInitPromise(); // App is now fully initialized ``` For synchronous checks: ```javascript if (SvApp.hasShared() && SvApp.shared().hasDoneAppInit()) { // Safe to interact with app } ``` ## Notifications - **`"appDidInit"`** — Posted when initialization is complete. Useful for observers that need to run setup code after the app is ready. - **`"onAppDeveloperModeChangedNote"`** — Posted when developer mode is toggled. --- Source: /docs/Lifecycle/Boot%20Sequence/ # Boot Sequence How the application loads from first script tag to a running app. ## Overview The boot process has three phases: load the boot loader, load all resources via the content-addressable memory system, then create and run the app. Each phase completes fully before the next begins, so by the time application code runs, every class and resource is available. ## 1. Initial Bootstrap The browser loads the boot loader script. Core boot files are loaded in parallel and evaluated sequentially. These include: Object extensions, Promise extensions, IndexedDB support, HashCache, and ResourceManager. ## 2. Resource Loading `ResourceManager.shared().setupAndRun()` drives this phase: 1. Loads `_index.json` containing resource metadata and content hashes. 2. Checks `HashCache` for cached resources — unchanged content is never re-downloaded. 3. Downloads `_cam.json.zip` (compressed content bundle) on cache miss. 4. Evaluates CSS resources sequentially (cascade order matters). 5. Filters out environment-inappropriate resources (e.g., `browser-only/` files in Node.js) via `StrvctFile.canUseInCurrentEnv()`. 6. Evaluates JavaScript resources in dependency order. After this phase, all classes are defined and available globally. See [Headless Execution](../Headless%20Execution/index.html) for details on how the boot sequence adapts to Node.js. ## 3. App Creation `SvApp.loadAndRunShared()` creates the app singleton and opens the persistent store: ``` SvApp.loadAndRunShared() ├── setStore(defaultStore()) ├── loadFromStore() │ ├── store.promiseOpen() │ └── store.rootOrIfAbsentFromClosure() └── run() └── setup() ├── pauseSchedulers() ├── setupModel() ├── setupUi() ├── appDidInit() │ ├── setHasDoneAppInit(true) │ ├── postNoteNamed("appDidInit") │ ├── unhideRootView() │ └── afterAppUiDidInit() │ ├── handleSearchParams() │ └── didInitPromise.resolve() └── resumeSchedulers() ``` Schedulers are paused during setup so that model and UI initialization don't trigger premature sync cycles. They resume once everything is wired up. --- Source: /docs/Lifecycle/Headless%20Execution/ # Headless Execution Running STRVCT applications in Node.js without a browser. ## Overview STRVCT's model layer, persistence system, and boot sequence all work in Node.js. The same application code that runs in the browser can run headlessly — with the same storage behavior, the same object lifecycle, and the same slot system — just without a DOM or view layer. This is useful for testing, server-side processing, and CLI tools. The key enabling design is that the model never references views. Views observe the model through notifications, but the model has no knowledge of views. Remove the view layer and everything else continues to work. ## Environment Detection `SvPlatform.isNodePlatform()` is the primary environment check. It detects Node.js by testing for the presence of `process.versions.node`. The inverse, `SvPlatform.isBrowserPlatform()`, returns true when not in Node.js. Framework code that needs to behave differently per environment uses this check at runtime: ```javascript if (SvPlatform.isNodePlatform()) { // Node.js-specific behavior } else { // Browser-specific behavior } ``` However, most environment branching doesn't happen at the call site — it happens through the resource loading system, which selects the right implementation automatically. ## Environment-Specific Resources STRVCT uses a path-based convention to include or exclude resources based on the runtime environment. Any directory named `browser-only` or `server-only` in a resource path triggers automatic filtering during the boot sequence. ### How It Works During resource loading, `ResourceManager` calls `StrvctFile.canUseInCurrentEnv()` on each resource. This method splits the file path into components and checks for the convention directories: - In **Node.js**: files with `browser-only` in their path are skipped. - In the **browser**: files with `server-only` in their path are skipped. All other files load in both environments. ### The Same-Name Class Pattern The most important use of this convention is providing environment-specific implementations of the same class. For example, `SvIndexedDbFolder` exists in two locations: ``` source/boot/browser-only/SvIndexedDbFolder.js # Uses native IndexedDB source/boot/server-only/SvIndexedDbFolder.js # Uses LevelDB via classic-level ``` Both define a class named `SvIndexedDbFolder` with the same API. In the browser, the browser-only version loads and the server-only version is skipped. In Node.js, the reverse happens. Code that depends on `SvIndexedDbFolder` doesn't need to know which implementation it's using — the interface is identical. This pattern is also used in `external-libs/`. For example, `simple-peer` (a WebRTC library) lives in `external-libs/browser-only/simple-peer/` and is automatically excluded in Node.js where WebRTC isn't available. ### Directory Structure Examples ``` strvct/source/boot/ browser-only/ SvIndexedDbFolder.js # IndexedDB implementation server-only/ SvIndexedDbFolder.js # LevelDB implementation strvct/external-libs/ browser-only/ simple-peer/ # WebRTC — browser only pako/ # Compression — both environments js-sha256/ # Hashing — both environments ``` ## Storage Abstraction The persistence system works identically in both environments because the storage backend is abstracted behind `SvIndexedDbFolder`. ### The Layer Stack ``` Application code ↓ SvObjectPool / SvPersistentObjectPool — object cache, dirty tracking, GC ↓ SvPersistentAtomicMap — synchronous in-memory cache, batched writes ↓ SvIndexedDbFolder — environment-specific storage backend ↓ IndexedDB (browser) or LevelDB (Node.js) ``` ### Why Everything Loads Into Memory IndexedDB is entirely asynchronous — every read requires a callback or promise. But the object graph needs synchronous access: when a slot getter runs during deserialization or normal code, it can't `await` a database read. `SvPersistentAtomicMap` resolves this by loading the entire object store into a JavaScript `Map` on open. After that, all reads are synchronous map lookups and all writes go to an in-memory change set. On commit, changes are flushed to the underlying store in a single atomic transaction. This design means the layers above `SvIndexedDbFolder` never make async storage calls during normal operation — the async boundary is only at open and commit time. The tradeoff is that the full object dataset must fit in memory, which is fine for typical object graphs (tens of megabytes) but wouldn't work for large binary data. ### Blob Storage Is Separate and Async Binary data — images, audio, and other media — is stored separately in `SvBlobPool`, a content-addressable blob store that uses its own `SvIndexedDbFolder` instance (database name `defaultBlobStore`, separate from the object pool's `defaultDataStore`). Unlike the object pool, blob storage is fully asynchronous: blobs are read and written individually on demand, not loaded into memory all at once. This keeps memory usage proportional to what the application is actively using rather than the total size of all stored media. `SvBlobPool` also works in headless mode — it uses the same `SvIndexedDbFolder` abstraction, so it gets the LevelDB backend automatically in Node.js. See [Local and Cloud Blob Storage](../../Persistence/Local%20and%20Cloud%20Blob%20Storage/index.html) for details on the blob system. ### Browser Backend The browser implementation uses the native IndexedDB API. It opens a database, creates an object store, and provides `promiseAt()`, `promiseAtPut()`, and transaction methods that map directly to IndexedDB operations. ### Node.js Backend The Node.js implementation uses [LevelDB](https://github.com/google/leveldb) via the `classic-level` npm package. It stores data in a filesystem directory (defaulting to `./data/leveldb/`). The same async API — `promiseAt()`, `promiseAtPut()`, transactions — is implemented on top of LevelDB's batch operations. String values are stored with a byte marker prefix to distinguish them from binary data, since LevelDB's native format is binary buffers. ### What This Means in Practice Application code — model classes, slot definitions, `setShouldStore(true)`, dirty tracking — is completely unaware of which backend is in use. The same `Contact` class with the same slot annotations stores to IndexedDB in Chrome and to LevelDB in Node.js, with identical serialization format and identical commit behavior. See [Local Object Pools](../../Persistence/Local%20Object%20Pools/index.html) for details on the persistence system itself. ## External Libraries STRVCT vendors its external dependencies as source files in `external-libs/` rather than using npm. These libraries are declared in `_imports.json` files and go through the same CAM (Content-Addressable Memory) build process as framework code. At runtime, they're loaded and evaluated by the same `ResourceManager` that handles everything else. This means external libraries automatically work in both environments — the resource loader evaluates them from the CAM bundle in the browser, or from the filesystem in Node.js. Libraries that are browser-specific (like `simple-peer` for WebRTC) use the `browser-only/` directory convention to be excluded in Node.js. Current vendored libraries include: pako (compression), htmlparser2, jwt-decode, js-sha256, simple-peer (browser-only), and several JSON utilities (ajv, fast-json-patch, jsonrepair, jsondiffpatch). ## API Shims and Environment-Specific Implementations Each environment lacks some APIs that the other provides natively. Rather than simulating a complete browser or Node.js environment, STRVCT provides minimal shims for specific APIs and uses environment-specific implementations where needed. Major DOM objects like `document` and `window` are **not** polyfilled — code that depends on the DOM should be in `browser-only/` directories or guarded by `SvPlatform.isBrowserPlatform()` checks. ### Browser API Shims for Node.js These live in `source/library/ideal/categories/server-only/` and provide browser APIs that don't exist natively in Node.js: | Shim | Browser API replaced | Node.js implementation | |------|---------------------|----------------------| | `XMLHttpRequestShim.js` | XMLHttpRequest | Wraps the `xhr2` package | | `FileReaderShim.js` | FileReader | Reads Blobs and Buffers as text, data URLs, or ArrayBuffers | | `RangeShim.js` | Range | Minimal stub (no actual DOM manipulation) | | `ImageShim.js` | Image, HTMLCanvasElement | Uses the `canvas` package | | `FontFaceShim.js` | FontFace | Stub that accepts font declarations without loading | The boot sequence also sets up a minimal `performance` object (with `performance.now()` backed by `Date.now()`) if one isn't already available. ### Environment-Specific Implementations Some functionality has different best-available implementations per environment. These use the same-name class pattern — matching files in `browser-only/` and `server-only/` with the same class name and API: | File | Browser implementation | Node.js implementation | |------|----------------------|----------------------| | `Number_random.js` | `Math.random()` | Node.js `crypto` module | | `SvIndexedDbFolder.js` | Native IndexedDB | LevelDB via `classic-level` | ### APIs Available Natively in Both Several APIs that STRVCT uses are available in modern Node.js and don't need shims: `Blob` (v18+), `URL` (v10+), `TextEncoder`/`TextDecoder` (v11+), `crypto` (native module), and `ArrayBuffer` (native JavaScript). ## Practical Applications Headless execution enables several workflows: - **Automated testing** — run model-layer tests in Node.js without browser overhead - **Server-side processing** — load and manipulate the same object graphs that the browser uses - **CLI tools** — the STRVCT boot system itself (`ImportsIndexer`, `SvResourceIndexer`) runs headlessly - **Build pipelines** — static site generation and other build-time processing can use the full framework --- Source: /docs/Lifecycle/Node%20Lifecycle/ # Node Lifecycle Creation, three-phase initialization, parent-child relationships, and update notifications. ## Class Hierarchy ``` Object → ProtoClass → SvNode → SvViewableNode → SvStyledNode → SvStorableNode ``` ## Three-Phase Initialization Every node goes through the same sequence, whether freshly created or loaded from storage: ### 1. `init()` — Primitives and slots - Calls `super.init()` - Initializes slots via `initializeSlots()` - Sets up notification observers - Configures mutation watching Only set primitive values and simple defaults here. Don't create child objects — they may be about to arrive from the store. ### 2. `finalInit()` — Complex objects and relationships - Calls `super.finalInit()` - Creates objects for slots with `setFinalInitProto()` — but only if the slot wasn't already populated from storage - Establishes object relationships This is where you detect whether the object is new or loaded: ```javascript finalInit () { super.finalInit(); if (!this.hasStoredData()) { this.randomizeValues(); } } ``` ### 3. `afterInit()` → `didInit()` — Post-initialization - Sets `_hasDoneInit = true` - Triggers initial notifications - Can be scheduled for end of event loop By this point the full object graph is ready and it's safe to interact with other objects. ## Creating Instances Always use `ProtoClass.clone()` rather than `new`. `clone()` runs the full three-phase sequence automatically. ## Parent-Child Relationships **Adding subnodes:** - `addSubnode(node)` — Add to end - `addSubnodeAt(node, index)` — Add at specific position - Automatically sets `parentNode` - Triggers `didChangeSubnodeList()` **Removing subnodes:** - `removeSubnode(node)` — Remove specific node - Clears `parentNode` reference - Triggers `didChangeSubnodeList()` ## Update Notifications When a slot value changes, the framework fires a notification chain: ``` setSlotValue(newValue) └── didUpdateSlot(slot, oldValue, newValue) ├── didUpdateSlot[SlotName](oldValue, newValue) └── didMutate() [for storable nodes] ``` Additional update methods: - `didUpdateNode()` — Manual update trigger - `didUpdateNodeIfInitialized()` — Only fires if `hasDoneInit()` is true, preventing spurious notifications during setup - `didChangeSubnodeList()` — Fired when the subnodes array changes ## Conditional Updates A common pattern is to guard update logic so it only runs after initialization is complete: ```javascript didUpdateSlotFoo (oldValue, newValue) { if (this.hasDoneInit()) { this.didUpdateNode(); } } ``` --- Source: /docs/Lifecycle/Persistence%20Lifecycle/ # Persistence Lifecycle How objects are stored, loaded, and kept in sync with IndexedDB. ## Overview Persistence in STRVCT is automatic. Objects marked as storable are tracked for changes, serialized at the end of each event loop, and written to IndexedDB. On reload, they're deserialized and re-initialized through the same three-phase lifecycle as new objects. The goal is transparency — a class shouldn't need different code paths for "new" and "loaded." ## Storing Objects ### Marking Dirty When a storable slot changes, the framework automatically marks the object as dirty: 1. `didMutate()` is called from the slot's `didUpdateSlot()` hook. 2. The object is added to the store's dirty set. 3. At the end of the event loop, all dirty objects are auto-committed. ### Serialization ``` recordForStore(store) ├── Serialize slots marked with setShouldStoreSlot(true) ├── Convert object references to puuids └── Return JSON-compatible record ``` Only slots explicitly marked with `setShouldStoreSlot(true)` are persisted. Object references are stored as persistent unique IDs (puuids) rather than direct pointers, so the full object graph can be reconstructed on load. ## Loading Objects Loading reverses the process in three steps: ### 1. Instance Creation ``` instanceFromRecordInStore(record, store) ├── Create blank instance ├── Call init() └── Return for population ``` ### 2. Data Population ``` loadFromRecord(record, store) ├── Restore slot values ├── Resolve object references via puuids └── Skip slots with setFinalInitProto() (they'll be handled next) ``` ### 3. Relationship Restoration `finalInit()` re-establishes complex relationships. Slots configured with `setFinalInitProto()` only create default instances if the slot wasn't already populated from the store — this is the key mechanism that makes the three-phase lifecycle work for both new and loaded objects. ## Persistence-Aware Initialization When you need initialization logic that only runs for newly created objects (not loaded ones): ```javascript finalInit () { super.finalInit(); if (!this.hasStoredData()) { this.randomizeValues(); } } ``` ## Store Events - `didLoadFromStore(store)` — Called after all objects have been loaded and initialized. Safe to access the full object graph. - `willStore()` — Called just before serialization. Optional hook for pre-save cleanup. ## Clean Shutdown ```javascript shutdown () { this.stopWatchingNode(); this.subnodes().forEach(node => node.shutdown()); this.removeFromParent(); } ``` --- Source: /docs/Lifecycle/View%20Synchronization/ # View Synchronization How views discover nodes, synchronize state, and batch updates efficiently. ## View Discovery The framework creates views automatically when a node needs to be displayed: 1. The node calls `nodeViewClass()`. 2. The framework searches for `NodeClassNameView` or `NodeClassNameTile`. 3. Falls back up the inheritance chain until a match is found. 4. Creates an instance via `clone()` and associates it via `setNode()`. ``` SvNodeView.newSubviewForSubnode(node) ├── Determine view class ├── Create instance via clone() └── Associate via setNode() ``` ## Model to View When a node property marked with `setSyncsToView(true)` changes: 1. The node's `didUpdateSlot()` fires. 2. An `onUpdatedNode` notification is posted. 3. The observing view schedules `syncFromNode()` via `SvSyncScheduler` at priority 2. 4. At the end of the event loop, the view updates its DOM and subviews. ## View to Model When the user interacts with a view: 1. The view calls an action method on the node. 2. The node updates its internal state. 3. This triggers a model-to-view sync, completing the cycle. View-to-model syncs run at priority 0 (higher than model-to-view), ensuring user edits are applied before any reactive updates. ## The Sync Scheduler `SvSyncScheduler` is central to performance: - **Batching** — Multiple slot changes in the same event loop produce a single sync call, not one per change. - **Coalescing** — Duplicate sync requests for the same view are collapsed. - **Loop detection** — The scheduler detects and breaks infinite sync cycles. - **Pausing** — Schedulers can be paused during bulk operations (e.g. app startup) and resumed afterward. ```javascript // Multiple updates, single sync cycle this.scheduleSyncToView(); this.otherProperty().scheduleSyncToView(); // Both handled in one pass ``` ## View Lifecycle Events - `willAddSubview(subview)` — Before adding a child view - `willRemoveSubview(subview)` — Before removing a child view - `willRemove()` — Before this view is removed from its parent - `onVisibility()` — Element becomes visible (via IntersectionObserver) - `didChangeNode()` — The node reference changed ## Performance - **Lazy creation** — Views are only created when their node becomes visible in the navigation. - **Visibility tracking** — `IntersectionObserver` integration means off-screen views can skip expensive updates. - **Batched updates** — The sync scheduler ensures that rapid state changes don't cause redundant DOM work. --- Source: /docs/Naked%20Objects/ # Naked Objects Closing the Usability Gap in Naked Objects ## Abstract Also available as a [PDF](compiled/Closing_the_Usability_Gap_in_Naked_Objects.pdf). The naked objects pattern proposed that user interfaces could be generated directly from domain models, eliminating the cost of bespoke UI development while guaranteeing consistency between interface and business logic. Twenty-five years later, this promise remains largely unfulfilled — not because automatic generation failed, but because the generated interfaces lacked the spatial organization and navigational fluency that users expect. This paper argues that the design space for presenting structured information is far narrower than the software industry assumes, and that a small set of composable UI primitives — tiles, tile stacks, and recursively nested master-detail views — can cover it. We present Strvct, an open-source JavaScript framework implementing this approach, and show that centralizing the model-to-view pipeline produces emergent capabilities — transparent internationalization, automatic accessibility, annotation-driven persistence with cloud sync, and automatic schema generation — that would require significant per-component effort in conventional frameworks. ## 1. Introduction Most application frameworks treat the user interface as a separate engineering problem from the domain model. Each screen must be designed, implemented, and maintained independently. Every new model class or schema change propagates into view code, form layouts, navigation logic, and responsive design. This duplication is not incidental — it is structural, and its cost grows linearly with the number of domain objects. Naked objects [1] proposed a radical alternative: expose domain objects directly to users and generate the interface automatically. Developers write only the domain model; the UI follows from it. This also guarantees structural consistency — the interface always reflects the actual state and shape of the model, because there is no separate representation to fall out of sync. The pattern was described by Pawson and Matthews in 2000 and has been implemented in several frameworks, most notably Apache Isis (now Apache Causeway) for Java [3]. These implementations demonstrated the core thesis: automatic UI generation from domain models is feasible and produces functionally complete interfaces. Yet adoption has remained confined to internal tools, administrative interfaces, and prototypes. The reason is not technical but perceptual. The generated interfaces — generic forms for objects, tables for collections, menus for navigation — are correct and complete but *feel wrong*. They lack the spatial hierarchy, navigational depth, and responsive behavior that users have come to expect. They resemble database administration tools more than the applications people use daily. This usability gap, rather than any limitation of the underlying pattern, has prevented naked objects from reaching its potential. This paper describes an approach to closing that gap. We argue that the design space for presenting structured information is narrower than it appears, identify a small set of composable primitives that covers it, and present a framework — Strvct — that demonstrates the approach in a production application. ## 2. The Usability Gap Prior naked objects implementations typically present each object as a form with fields for its properties, and collections as tables or lists. Navigation is handled through menus, links, or search. This strategy is functionally sufficient but creates four specific problems: **Lack of spatial hierarchy.** Users expect spatial relationships to convey meaning: hierarchy expressed top-to-bottom, navigation depth expressed left-to-right, containment for ownership. Generic forms and tables flatten these relationships, requiring users to navigate through menus rather than perceiving structure visually. **No viewport adaptation.** Modern applications invest heavily in responsive design — collapsing navigation, stacking layouts, hiding secondary content. Generic form-based interfaces either ignore viewport constraints entirely or implement ad-hoc responsive behavior that doesn't generalize across the domain model. **Inconsistent navigation depth.** As users navigate deeper into an object hierarchy, form-based interfaces either replace the current view entirely (losing context) or open new windows (fragmenting context). Neither gives users a sense of where they are within the larger structure. **No visual continuity.** Without a consistent spatial model, users cannot build a mental map of the application. Each navigation action feels like arriving at a new, disconnected screen rather than moving within a coherent space. These problems are not inherent to the naked objects pattern. They are artifacts of a UI strategy — generic forms and tables — that prior implementations chose because it was simple and sufficient for their target audience. The question is whether a different strategy can retain the benefits of automatic generation while producing interfaces that meet the expectations set by modern hand-crafted applications. ## 3. A Narrow Design Space Before proposing a specific solution, it is worth examining what those expectations actually entail. When you survey informational interfaces across applications, websites, and operating systems, the same spatial conventions appear repeatedly: hierarchy expressed top-to-bottom, navigation depth expressed left-to-right, containment for ownership, and lists for collections. Consider three examples: - **macOS Finder** (Miller Columns): a horizontal chain of list panels, each showing the contents of the item selected in the panel to its left. Selecting a folder reveals its contents in the next column. The spatial metaphor is depth as horizontal position. - **iOS Settings**: a vertical list of categories. Selecting one pushes a new list onto a navigation stack, with a back button to return. The spatial metaphor is depth as screen replacement with a linear path. - **Slack**: a vertical list of channels on the left, message content on the right, thread detail in a panel further right. The spatial metaphor is the same master-detail pattern at two levels of nesting. These are three different applications built by three different teams for three different purposes, yet they use the same underlying spatial logic. This is not coincidence. These conventions are inherited from how we organize written information — the same top-to-bottom, left-to-right reading order found in Western text. They are so deeply embedded in interface culture that deviating from them creates confusion rather than innovation. Bespoke UI developers are already converging on these patterns — unconsciously and inconsistently. The variation between hand-crafted interfaces is largely superficial: different visual styling, different spacing, different component libraries, but the same underlying spatial logic. Most of what distinguishes one application's navigation from another's, at the structural level, is accidental rather than essential. This observation has a practical consequence: if the design space is narrow, a framework that applies these conventions uniformly may produce interfaces that are not merely acceptable but *preferable* to a patchwork of bespoke screens, because the user can rely on consistent navigation throughout the application. The framework's limitation — it cannot produce arbitrary layouts — is actually an advantage, because arbitrary layouts are precisely what creates inconsistency. There are, of course, interfaces that fall outside this narrow space: data visualizations, design canvases, game renderers, media editors. These require domain-specific rendering that cannot be derived from model structure alone. But these are a minority of the screens in most applications. The majority — settings, lists, forms, inspectors, hierarchical browsers — are well within the space that a small set of composable primitives can cover. ## 4. Approach: Composable UI Primitives Our approach is to define a small set of composable UI primitives that embody the spatial conventions identified above. Each primitive handles one aspect of presentation; composed together, they cover the navigational and layout patterns found in typical informational applications. ### 4.1 Tiles The fundamental unit of presentation is the **tile**: a view that presents a single domain object or a single property of a domain object. **Summary tiles** present domain objects with a title, subtitle, and optional sidebars. They serve as the primary navigation element: selecting a summary tile reveals the object's contents in an adjacent detail area.
Summary SvTile
[SVG diagram]
**Property tiles** present individual properties as key-value pairs, with optional notes and validation errors. Specialized property tiles handle common types — strings, numbers, dates, images, booleans — with type-appropriate editing interactions.
Property SvTile
[SVG diagram]
Tiles support gestures for direct manipulation: slide-to-delete, long-press reordering, and drag-and-drop between tile stacks or across browser windows. Domain objects register which MIME types they accept, enabling type-safe import and export through standard drag interactions. Tiles can be subclassed for domain-specific presentation, but the default tiles are designed to be sufficient for the majority of cases. The goal is to make custom tiles the exception, not the rule. ### 4.2 SvTile Stacks A **tile stack** is a scrollable, ordered sequence of tiles presenting the subnodes of a domain object. SvTile stacks support flexible orientation (vertical or horizontal) and gestures for adding, removing, and reordering items.
SvTile Stack
[SVG diagram]
### 4.3 Master-Detail Views A **master-detail view** pairs a tile stack (the master) with a detail area that presents the currently selected item. The detail area itself may contain another master-detail view, enabling arbitrarily deep navigation through recursive composition.
Master-Detail View
[SVG diagram]
Three features make this composition practical: **Flexible orientation.** The detail area can be positioned to the right of or below the master, as specified by the domain object or overridden by the interface. This allows the same primitive to express both horizontal navigation (like a file manager) and vertical drill-down (like a settings panel).
Master-Detail Orientations
[SVG diagram]
**Automatic collapsing.** When the viewport is too narrow to display the full chain of master-detail views, earlier columns automatically collapse. A breadcrumb bar tracks the navigation path and provides back-navigation. The same structure works on a wide desktop monitor and a narrow mobile screen without any per-object responsive design.
Expanded
[SVG diagram]

Collapsed
[SVG diagram]

**Header and footer areas.** The master section supports optional header and footer views for features like search, message input, or group actions, allowing common interaction patterns to be expressed within the same compositional framework. ### 4.4 Composition Nesting master-detail views with varying orientations produces navigation structures that match many common application patterns: Miller column file browsers, settings hierarchies, email clients, chat applications, inspector panels. These are not special cases implemented individually — they are natural compositions of the same three primitives.
Vertical
[SVG diagram]
Horizontal
[SVG diagram]
Hybrid
[SVG diagram]
This composability is the key insight. Rather than implementing a fixed set of application templates, the framework provides building blocks that compose to produce appropriate layouts for each part of the domain model. The Miller Column pattern [4] has been used since NeXTSTEP for file browsing; our contribution is making it recursive, orientation-flexible, and self-composing based on model annotations. ## 5. From Model to Interface To make the "write the model, get the UI" claim concrete, consider a minimal domain class in Strvct: ```javascript (class Character extends SvStorableNode { initPrototypeSlots () { { const slot = this.newSlot("name", ""); slot.setSlotType("String"); slot.setShouldStoreSlot(true); slot.setSyncsToView(true); slot.setCanEditInspection(true); } { const slot = this.newSlot("level", 1); slot.setSlotType("Number"); slot.setShouldStoreSlot(true); slot.setSyncsToView(true); } { const slot = this.newSlot("inventory", null); slot.setFinalInitProto(Inventory); slot.setIsSubnodeField(true); } } initPrototype () { this.setShouldStore(true); } subtitle () { return "Level " + this.level(); } }.initThisClass()); ``` This definition contains no UI code, no form layouts, no navigation logic, and no serialization code. Yet it produces: - A **summary tile** displaying the character's name as a title and "Level 3" as a subtitle - **Property tiles** for `name` (editable string field) and `level` (editable number field), with appropriate input types - A **navigable field** for `inventory` that, when selected, opens a new master-detail view showing the inventory's contents - **Automatic persistence** to IndexedDB, with dirty tracking and transactional commits - **Bidirectional synchronization** between model and view — editing a field updates the model; programmatic model changes update the view - **Automatic translation** of field labels and values when internationalization is active The slot annotations — `setShouldStoreSlot`, `setSyncsToView`, `setCanEditInspection`, `setIsSubnodeField` — are the bridge between the domain model and the framework's automatic behaviors. Each annotation controls one aspect of the object's lifecycle; together, they provide enough information for the UI, storage, and synchronization layers to operate without additional code. The following screenshot shows the Strvct framework as used in undreamedof.ai, an AI-powered virtual tabletop for tabletop roleplaying games. The interface — including character sheets, campaign hierarchies, session management, and settings panels — is generated from domain model annotations. No bespoke layout code was written for any of the screens shown. Screenshot of undreamedof.ai, a Strvct-based application ## 6. Architecture Strvct is implemented as a client-side JavaScript framework. Applications run as single-page apps in the browser, making heavy use of client-side persistent storage — both for caching code and resources via a content-addressable build system, and for maintaining a persistent object database of application state in IndexedDB. An important architectural distinction: Strvct does not compile or pre-render user interfaces. There is no build step that produces a view tree, no template system, and no static component hierarchy. Views are instantiated lazily at runtime — only when the user navigates to a node in the object graph. Each navigation step inspects the target node's class and slot annotations, discovers or creates an appropriate view, and binds it to the node for live bidirectional synchronization. Once created, a view persists as long as its node remains visible, staying in sync with the model through the notification system. The result is closer to a live object browser than a conventional render pipeline: the UI that exists at any moment is determined by the user's current navigation path through the object graph, and it responds immediately to changes in the underlying model. ### 6.1 Domain Model The domain model is a graph of objects inheriting from a common base class. Each object has properties declared as *slots* with annotations, actions exposed as methods, a `subnodes` array of child objects, a `parentNode` reference, and a unique persistent identifier. The model is fully independent of the UI layer. Model objects hold no references to views and communicate outward solely by posting notifications. This allows the same model code to run headlessly in Node.js for testing or server-side processing. ### 6.2 The Annotation Bridge The slot system is what makes automatic UI and storage possible. Rather than using raw instance variables, properties carry metadata annotations that each framework layer consults independently: - **Type** — selects the appropriate property tile and enables runtime type checking (every generated setter validates its argument against the declared type, catching type errors at the point of assignment rather than at compile time) - **Persistence** — includes the slot in storage records - **View synchronization** — triggers view updates when the value changes - **Subnode relationship** — controls whether the value appears in the object's navigable hierarchy - **Editability** — determines whether the property can be modified through the UI - **Auto-initialization** — specifies a class to instantiate if no value was loaded from storage - **Translation context** — provides semantic context for AI-powered translation No single annotation knows about the others. The UI layer reads type and editability; the storage layer reads persistence; the synchronization layer reads sync flags. This separation means new layers can be added — internationalization, cloud sync, schema generation — without modifying existing annotations or the domain model itself. ### 6.3 Storage Persistence is annotation-driven. The persistence layer monitors slot mutations, batches dirty objects at the end of each event loop into atomic transactions, and commits them to IndexedDB. On load, stored records are deserialized back into live object instances with relationships re-established. A separate content-addressable blob store handles large binary data using SHA-256 hashes as keys, providing automatic deduplication. Objects store hash references rather than blob data directly. Automatic garbage collection walks the stored object graph from the root; unreachable objects are removed. ### 6.4 Synchronization Model and view layers communicate through a deferred, deduplicated notification system. When a model property changes, a notification is posted; observing views schedule a sync pass. Multiple changes within a single event loop are coalesced. Bidirectional sync stops automatically when values converge, preventing infinite loops. Observations use weak references, so garbage collection of either party automatically cleans up subscriptions. ## 7. Emergent Capabilities When the framework controls the entire pipeline from model annotation to rendered view, capabilities that would normally require per-component effort become emergent properties of the architecture. Each capability described below exists because of the same structural fact: the framework has complete knowledge of the domain model and controls the single point where model data flows to the UI. ### 7.1 Transparent Internationalization Because all UI text flows from model slot annotations through a single rendering pipeline, translation can be injected at the model-to-view boundary transparently. No per-component `t()` calls or translation key files are needed. New model classes are translatable by default. This centralization also makes AI-powered translation practical. The framework discovers every translatable string by walking model class prototypes — an introspection that is only possible because all UI-visible text originates from annotated slots. Slot-level context annotations (e.g., "game mechanic" vs. "ui label") travel with the model, giving the AI translator domain-appropriate terminology without developer effort. Adding support for a new language becomes a configuration change rather than a translation project. In a conventional framework, achieving automatic translation would require either instrumenting every component with translation calls, or building an extraction tool that parses component templates to find translatable strings. Both approaches scale linearly with the number of components. In Strvct, the cost is zero per component — the framework handles it structurally. ### 7.2 Transparent Persistence and Cloud Sync Because the framework owns the complete object graph and understands the structure of every node through annotations, it can split persistence into two strategies transparently. The object pool — the graph of domain objects and their properties — is loaded synchronously, ensuring the model is immediately available for UI rendering. Large binary resources such as media and document files are stored separately in a content-addressable blob store and loaded asynchronously on demand, so they never block the UI. This same structural knowledge enables transparent cloud synchronization. Subgraphs of the local object database can be lazily synced to cloud storage without the developer writing any sync logic — the framework knows which objects have changed, which blobs they reference, and how to reconcile local and remote state. The developer annotates what should persist; the framework decides how and when to load, store, and sync it. ### 7.3 JSON Schema Generation Domain objects can automatically generate JSON Schema descriptions of themselves based on their slot annotations. This is particularly valuable for integration with large language models, which can use the schema to understand the structure of domain objects, validate their output, and generate patches or new instances that conform to the model's constraints. In Strvct's production use, AI assistants modify game state by generating JSON Patch operations validated against auto-generated schemas — a workflow that requires no hand-written schema definitions. ### 7.4 Automatic Accessibility Because the framework generates the view hierarchy from model annotations and controls the rendering of every tile, field, and navigation structure, it can emit ARIA roles, labels, and states automatically — without any accessibility-specific code from the developer. Each view class provides a default ARIA role derived from its function: tile stacks become `list` containers, individual tiles become `link` items, field tiles become `group` elements, boolean fields become `checkbox`, action fields become `button`. Slot metadata that already exists for type checking and schema generation — `isRequired()`, `isReadOnly()`, minimum/maximum constraints, `description()` — maps directly to ARIA attributes: `aria-required`, `aria-readonly`, `aria-valuemin`/`aria-valuemax`, `aria-description`. The node hierarchy provides landmarks (`navigation`, `region`) and breadcrumb structure. Dynamic content nodes can opt into `aria-live` announcements. The parallel to internationalization (Section 7.1) is exact. Both capabilities exist because all UI-visible information flows from annotated model slots through a single framework-controlled pipeline. In a conventional framework, accessibility is a per-component obligation — every button, list, and form field needs manual ARIA attributes, and the cost scales linearly with the number of components. In Strvct, fixing accessibility in the ~15 core view classes fixes it for every application built on the framework. ### 7.5 Content-Addressable Resource Loading The framework's build system produces a content-addressable bundle where source files are keyed by SHA-256 hash. The client caches resources locally in IndexedDB and uses hash comparison to determine what has changed between deployments. Unchanged resources are never re-downloaded, and identical content across different file paths is stored only once. This is a consequence of the framework controlling the full resource pipeline — analogous to how it controls the model-to-view pipeline — and provides caching granularity that standard bundlers cannot achieve. ## 8. Case Study: undreamedof.ai Strvct has been used to build undreamedof.ai, an AI-powered virtual tabletop for Dungeons & Dragons and other tabletop roleplaying games. The application includes: - **Character system**: hierarchical character sheets with ability scores, inventory, spellcasting, and equipment — approximately 30 domain classes - **Campaign system**: adventure management with nested locations, NPCs, creatures, treasures, and narrative structure — approximately 20 domain classes - **Session system**: real-time multiplayer game sessions with AI game master, dice rolling, voice narration, and peer-to-peer networking — approximately 25 domain classes - **AI integration**: multiple AI service providers, tool calling, streaming responses, and prompt composition — approximately 15 domain classes The application comprises roughly 90 domain model classes. Of these, fewer than 10 required custom view classes. The remainder — including character sheets, campaign hierarchies, settings panels, and administrative interfaces — use the framework's automatically generated views exclusively. This ratio — approximately 90% auto-generated views — is the practical test of the composable primitive approach. The domain model is non-trivial: character sheets have deeply nested hierarchies (character → ability scores → individual scores → modifiers), campaigns contain recursive location trees, and the session system manages real-time state across multiple connected clients. Despite this complexity, the default tiles and master-detail views produce navigable, usable interfaces throughout. The custom views that were needed fall into the category identified in Section 3 as outside the narrow design space: a chat interface for AI conversation, a 3D dice roller, and a map view. These are inherently graphical, domain-specific components that cannot be derived from model annotations. Their existence does not undermine the approach — it confirms that the boundary between auto-generated and bespoke views falls where predicted. ## 9. Related Work **Naked objects implementations.** Apache Isis (now Apache Causeway) [3] is the most mature naked objects framework, providing automatic UI generation for Java domain models with both a web UI (Wicket viewer) and a REST API. JMatter [5] implemented naked objects for Java Swing. Both use form-and-table UI strategies and target enterprise/administrative use cases. Strvct differs in its UI strategy (composable spatial primitives rather than forms and tables) and its target (end-user applications rather than internal tools). **Model-driven UI generation.** The broader field of model-driven development has produced approaches like IFML [6] (Interaction Flow Modeling Language) and UsiXML, which use abstract UI models to generate interfaces. These typically require separate UI models in addition to domain models — a layer of specification that naked objects explicitly eliminates. Strvct's approach is closer to naked objects in that the domain model itself, annotated with metadata, is the only specification needed. **Miller Columns.** The column-based navigation pattern was introduced in NeXTSTEP and popularized by macOS Finder [4]. It provides spatial continuity when browsing hierarchical data. Strvct extends this pattern by making it recursive (columns can nest vertically or horizontally), orientation-flexible (each level can choose its own orientation), and self-composing (the layout is determined by model annotations rather than application code). **Low-code and no-code platforms.** Modern low-code platforms (Retool, Appsmith, OutSystems) aim to reduce UI development cost through visual builders and pre-built components. They approach the same problem as naked objects — reducing the cost of UI development — but from the opposite direction: rather than eliminating bespoke UI, they make bespoke UI faster to produce. The result is still a collection of individually designed screens that must be maintained as the data model evolves. Naked objects eliminates this maintenance cost entirely. **AI-generated UI.** Large language models can now generate UI code from natural language descriptions. This automates the *creation* of bespoke interfaces but does not address their *maintenance* — each generated screen is still a separate artifact that must be updated when the model changes. Naked objects is a fundamentally different approach: rather than automating the production of bespoke UIs, it eliminates the need for them. ## 10. Discussion ### The Crossover Point Hand-crafted interfaces may appear more polished early in an application's life, when the number of screens is small and each can receive individual design attention. But as the domain model grows, the cost of maintaining bespoke screens grows with it, while inconsistencies accumulate. At some point — the crossover point — a consistent, automatically generated interface produces a better user experience than a patchwork of hand-crafted screens, because the user can rely on uniform navigation throughout the application. The composable primitive approach shifts this crossover point earlier by improving the quality of the generated interface. The undreamedof.ai case study suggests the crossover may occur sooner than expected: at ~90 domain classes, auto-generated views were not merely acceptable but preferred for 90% of the interface, because they provided consistent navigation and interaction patterns that would have been difficult to maintain across hand-crafted screens. ### A Familiar Dynamic This dynamic — general methods outperforming hand-crafted solutions as scale increases — appears in other domains. In his essay *The Bitter Lesson* [2], Rich Sutton observed that across 70 years of AI research, general methods that leverage computation consistently outperformed approaches that encoded human expertise, and the gap widened with scale. The analogy to naked objects is instructive if imperfect. In AI, the scaling dimension is computation; in naked objects, it is domain model complexity. The principle is similar: investing in a general method that improves with scale — rather than in case-by-case engineering that must be repeated for each new component — produces compounding returns. But unlike AI scaling, which depends on hardware improvements, naked objects scaling depends on a design choice: whether the framework's primitives are good enough to make hand-crafted specialization unnecessary for most screens. ### Strengths The approach is strongest in domains where the model itself is the volatile, high-value artifact — where requirements change frequently and the cost of keeping UI, storage, and synchronization in sync with a shifting model is the main engineering bottleneck. Because a model change propagates automatically to the UI, persistence, cloud sync, schema generation, and internationalization, the iteration cycle from "requirement changed" to "working software" is compressed to the time it takes to modify a class definition. Adding a property, restructuring a hierarchy, or introducing a new entity requires no corresponding changes to view code, form layouts, serialization logic, or API schemas. This makes the approach particularly suited to exploratory or fast-evolving applications — tools for analysis, research, operations, or any domain where the data model is expected to grow and change throughout the application's life. The framework's headless execution capability reinforces this: the same model that drives the UI can be tested, simulated, or batch-processed in Node.js without any browser dependency, enabling rapid validation of model changes before they reach users. ### Limitations The composable primitive approach is best suited to informational and navigational interfaces — applications centered on browsing, editing, and managing structured data. Highly graphical interfaces (data visualizations, design canvases, game renderers) require domain-specific rendering that cannot be derived from model annotations. Strvct supports custom view classes for these cases, but they fall outside the automatic generation pipeline. The spatial conventions we rely on — top-to-bottom hierarchy, left-to-right depth — reflect Western reading order. Right-to-left languages would require mirrored layouts, which the framework's flexbox-based rendering can accommodate but which have not yet been fully validated. ## 11. Conclusion The naked objects pattern has offered a compelling proposition for twenty-five years: write the domain model, and the rest follows. Its limited adoption is not a failure of this proposition but of the UI strategies that prior implementations chose. Generic forms and tables were sufficient for internal tools but did not meet the expectations set by modern consumer software. We have argued that the gap is closable because the design space is narrow. Bespoke UI developers are converging on a small set of spatial conventions; a framework can apply these conventions uniformly through composable primitives — tiles, tile stacks, and recursively nested master-detail views — that produce familiar, navigable interfaces from annotated domain models alone. Strvct demonstrates this approach in production. A non-trivial application with ~90 domain classes uses auto-generated views for approximately 90% of its interface, with custom views needed only for inherently graphical components that fall outside the narrow design space. The centralized model-to-view pipeline enables emergent capabilities — transparent internationalization, automatic accessibility, annotation-driven persistence with cloud sync, and automatic schema generation — that validate the architectural decision to invest in the general method. The challenge remains making the general method good enough that hand-crafted specialization becomes the exception rather than the rule. We believe the evidence presented here — a narrow design space, a small set of composable primitives, and a production application that confirms both — suggests this goal is within reach. ## References [1] Pawson, R. (2004). *Naked Objects.* PhD Thesis, Trinity College, Dublin. [2] Sutton, R. (2019). *The Bitter Lesson.* Incomplete Ideas. http://www.incompleteideas.net/IncIdeas/BitterLesson.html [3] Apache Software Foundation. *Apache Causeway* (formerly Apache Isis). https://causeway.apache.org/ [4] Becker, N. (2005). Miller Columns. Wikipedia. https://en.wikipedia.org/wiki/Miller_columns [5] Arteaga, J. M. *JMatter: A Naked Objects Framework for Java Swing.* http://jmatter.org/ [6] Brambilla, M., & Fraternali, P. (2014). Interaction Flow Modeling Language. In *Proceedings of the 23rd International Conference on World Wide Web (WWW '14 Companion).* ACM. --- Source: /docs/Notifications/ # Notifications Event-driven communication between framework layers using observations, scheduling, and weak references. STRVCT's notification system provides loose coupling between the model, view, and storage layers. Rather than holding direct references to each other, objects communicate through three complementary mechanisms: a deferred, deduplicated notification center for broadcast events; a sync scheduler that coalesces direct method calls; and a lightweight broadcaster for synchronous dispatch. All three use weak references for automatic cleanup — when an observer or sender is garbage collected, its registrations are removed without manual intervention. These are separate because they optimize for different tradeoffs. The notification center is loosely coupled — the sender doesn't know who's listening — but that generality has overhead: queuing, indexing, pattern matching. View synchronization doesn't need any of that; it needs a specific method on a specific object to run exactly once, which is what the sync scheduler provides directly. And some internal events (like storage-layer slot propagation) need synchronous delivery with no deferral at all, which is the broadcaster's role. A single mechanism couldn't serve all three without either over-engineering the simple cases or under-serving the complex ones. **Processing order within an event loop:** Application code runs first — any slot changes during this phase trigger slot change hooks immediately, and any Broadcaster dispatches also execute synchronously inline. Meanwhile, notifications and sync requests are queued, not executed. At the end of the event loop, the SyncScheduler processes its queue in priority order: view-to-model syncs (priority 0) run first, then notification dispatch (which delivers queued NotificationCenter events to observers), then model-to-view syncs (priority 2). Finally, the persistence layer auto-commits all dirty objects to IndexedDB. This ordering guarantees that user edits land before reactive updates, and that storage always sees the final settled state. - Notification Center - Sync Scheduler - Broadcaster - Slot Change Hooks --- Source: /docs/Notifications/Broadcaster/ # Broadcaster Lightweight synchronous dispatch for high-frequency internal events. ## Overview Some internal framework events — like storage-layer slot propagation — need to arrive immediately, not at the end of the event loop. Deferral would mean downstream code sees stale state between the change and the next microtask. The notification center's queuing and deduplication are liabilities here, not features. `SvBroadcaster` is a minimal synchronous alternative. Broadcasts execute immediately when posted — no queue, no deduplication, no event-loop delay. The tradeoff is less sophistication (no sender filtering, no pattern matching), which is fine for high-frequency internal events where the simplicity is the point. ## Usage ```javascript // Register a listener SvBroadcaster.shared().addListenerForName(this, "didChangeStoredSlot"); // Broadcast (immediate, synchronous) SvBroadcaster.shared().broadcastNameAndArgument("didChangeStoredSlot", this); ``` When the broadcast fires, the system calls the listener's method matching the broadcast name — in this case, `this.didChangeStoredSlot(argument)`. ## When to Use Broadcaster vs. Notification Center | | SvNotificationCenter | SvBroadcaster | |---|---|---| | **Timing** | Deferred to end of event loop | Immediate, synchronous | | **Deduplication** | Same name+sender dispatched once per loop | Every call dispatches | | **Registration** | SvObservation with name, sender, observer | Listener + name | | **Sender filtering** | Can observe a specific sender | Receives from all senders | | **Overhead** | Higher (queuing, indexing, dedup) | Lower | Use the notification center for most inter-layer communication — the deferred deduplication is almost always what you want. Use the broadcaster for internal framework events that need synchronous delivery and where the slight overhead of the notification center matters, such as storage-layer slot change propagation. --- Source: /docs/Notifications/Notification%20Center/ # Notification Center Deferred, deduplicated event dispatch with weak-reference cleanup. ## Overview In a framework where model, view, and storage layers must stay independent, objects need a way to announce state changes without knowing who cares. Direct method calls create tight coupling — the sender has to hold a reference to every interested party, and adding a new observer means modifying the sender. The notification center solves this: senders post named notifications into a shared singleton, observers register interest by name, and the two sides never reference each other. Notifications are queued and dispatched at the end of the event loop, so multiple changes within a single operation result in a single dispatch. This deferred deduplication is critical for performance — a bulk model update that touches 50 slots produces one notification pass, not 50. ## Posting Notifications Nodes post notifications when their state changes. The most common pattern uses the convenience method on `SvNode`: ```javascript this.postNoteNamed("myNotification"); ``` For notifications with additional data: ```javascript SvNotificationCenter.shared().newNote() .setSender(this) .setName("myNotification") .setInfo({ key: "value" }) .post(); ``` Notifications are not dispatched immediately. They are queued and processed at the end of the current event loop via `SvSyncScheduler`. If the same notification (same name and sender) is posted multiple times within one event loop, it is dispatched only once. ## Observing Notifications To observe notifications from a specific sender: ```javascript const obs = SvNotificationCenter.shared().newObservation() .setName("didUpdateNode") .setSender(targetNode) .setObserver(this) .startWatching(); ``` When `targetNode` posts a `"didUpdateNode"` notification, the system calls `this.didUpdateNode(note)` — by default, the method name matches the notification name. To use a different method: ```javascript obs.setSendName("handleNodeUpdate"); ``` For one-time observations that automatically stop after the first match: ```javascript this.watchOnceForNote("appDidInit").then(() => { // App is ready }); ``` Observations can also match broadly: - `setName(null)` — match notifications with any name from the specified sender - `setSender(null)` — match notifications with the specified name from any sender - Both null — match all notifications (useful for debugging) ## SvNotification Each notification is an `SvNotification` instance with three properties: - `name` — a string identifying the event (e.g. `"appDidInit"`, `"didUpdateNode"`) - `sender` — the object that posted it - `info` — optional additional data Notifications are deduplicated by a hash computed from `name` and `sender`. Posting the same event from the same object multiple times per event loop has no additional cost. ## SvObservation An observation connects a notification pattern to an observer: - `name` — notification name to match (or null for any) - `sender` — sender to match (or null for any), stored as a **weak reference** - `observer` — the object to notify, stored as a **weak reference** - `sendName` — optional custom method name to call on the observer - `isOneShot` — if true, automatically stops watching after the first match The notification center maintains indexes by sender and by name for fast matching. When a notification is dispatched, matching observations are found by intersecting the relevant index sets. ## Weak References and Automatic Cleanup Both `sender` and `observer` on `SvObservation` are stored as weak references. This means: 1. Observations do not prevent garbage collection of either party. 2. When a sender or observer is collected, a weak reference finalizer fires. 3. The finalizer schedules `stopWatching()` via `SvSyncScheduler`. 4. The observation is removed from the notification center automatically. This eliminates a common source of memory leaks in observer patterns. There is no need to manually remove observations when objects are destroyed. Descriptions of the sender and observer are captured at registration time so that debugging information remains available even after the objects have been collected. ## Common Notification Names **Application lifecycle:** - `"appDidInit"` — application fully initialized and ready **Node changes:** - `"didUpdateNode"` — node data changed - `"shouldFocusSubnode"` — request UI focus on a subnode - `"didReorderParentSubnodes"` — subnode order changed **Browser events:** - `"onBrowserOnline"` / `"onBrowserOffline"` — connectivity changes - `"onDocumentBeforeUnload"` — page unloading **Configuration:** - `"onAppDeveloperModeChangedNote"` — developer mode toggled Applications define additional notification names for their domain-specific events. --- Source: /docs/Notifications/Slot%20Change%20Hooks/ # Slot Change Hooks Automatic per-slot callbacks that connect property changes to notifications and persistence. ## Overview Without slot change hooks, every piece of code that modifies a property would also have to remember to post a notification, mark the object dirty for persistence, and trigger a view sync. That's three separate concerns that would have to be repeated at every call site — and forgetting any one of them causes silent bugs (stale views, lost data, missed observers). Slot change hooks centralize this. When a slot value changes, the framework automatically calls a hook method on the owning object. This single integration point connects the slot system to notifications, persistence, and view synchronization — the developer changes a value, and everything downstream happens automatically. ## The Hook Convention Define a method named `didUpdateSlot` + the capitalized slot name: ```javascript initPrototypeSlots () { { const slot = this.newSlot("health", 100); } } // Called automatically when health changes didUpdateSlotHealth (oldValue, newValue) { this.postNoteNamed("healthChanged"); } ``` The hook receives the previous and new values. It fires after the slot has been set, so `this.health()` already returns `newValue` inside the hook. ## What Triggers a Hook Hooks only fire when the value actually changes. Setting a slot to its current value is a no-op — no hook, no notification, no mutation tracking. This is what prevents infinite sync loops in bidirectional model-view synchronization. ## The General Hook In addition to per-slot hooks, every slot change calls the general `didUpdateSlot(slot, oldValue, newValue)` method. This is useful for cross-cutting concerns like logging or validation: ```javascript didUpdateSlot (slot, oldValue, newValue) { super.didUpdateSlot(slot, oldValue, newValue); console.log(slot.name() + " changed:", oldValue, "→", newValue); } ``` ## Connection to Persistence For storable nodes, `didUpdateSlot` also calls `didMutate()`, which marks the object as dirty in the persistent store. The store auto-commits dirty objects at the end of the event loop. This is how slot changes automatically propagate to IndexedDB — no manual save calls needed. ## Connection to Views Slots configured with `setSyncsToView(true)` trigger view synchronization when they change. The hook posts an `onUpdatedNode` notification, observing views schedule a `syncFromNode()` call, and the sync scheduler batches it to the end of the event loop. The full chain: ``` slot value changes → didUpdateSlot fires → didMutate() marks object dirty (persistence) → onUpdatedNode notification posted (views) → observing views schedule syncFromNode() → SvSyncScheduler batches and executes ``` --- Source: /docs/Notifications/Sync%20Scheduler/ # Sync Scheduler Deferred, deduplicated method calls with priority ordering and loop detection. ## Overview View synchronization and storage writes don't need the notification center's broadcast generality — there's no unknown set of observers, just a specific method on a specific object that needs to run. But without coalescing, the same sync could be requested dozens of times per event loop as multiple slots change. Calling it each time wastes work; calling it zero times loses data. `SvSyncScheduler` solves this by ensuring a given target/method pair runs exactly once per event loop, no matter how many code paths request it. It also provides priority ordering so that view-to-model syncs (user edits) complete before model-to-view syncs (reactive updates), preventing stale data from overwriting user input. The notification center uses `SvSyncScheduler` internally for its own dispatch, so notification delivery and view synchronization are handled in the same deferred processing pass. ## Scheduling Actions ```javascript SvSyncScheduler.shared().scheduleTargetAndMethod(this, "syncToView"); ``` This schedules `this.syncToView()` to run at the end of the current event loop. If the same target/method pair is scheduled again before it executes, the duplicate is ignored — the method runs exactly once. ## Priority Actions can be prioritized with an optional order parameter: ```javascript SvSyncScheduler.shared().scheduleTargetAndMethod(this, "syncToView", 100); ``` Higher values execute later. This is how the framework ensures view-to-model syncs (priority 0) complete before model-to-view syncs (priority 2) — user edits are applied before reactive updates. ## Immediate Processing For cases where deferred execution isn't appropriate: ```javascript SvSyncScheduler.shared().fullSyncNow(); ``` This synchronously processes all pending actions until the queue is empty. ## Loop Detection The scheduler detects infinite loops. If an action attempts to schedule itself while it is currently executing, an error is thrown: ``` SvSyncScheduler LOOP DETECTED: scheduleTargetAndMethod: (MyView.syncToNode) while processing: (MyView.syncToNode) ``` Bidirectional model-view synchronization avoids loops naturally because slot setters only trigger `didUpdateSlot` when the value actually changes. Setting a slot to its current value is a no-op. `fullSyncNow()` also includes a safety limit — it halts after 10 iterations if the queue hasn't drained, indicating a cycle that the single-action detector didn't catch. ## Pausing and Resuming Schedulers can be paused during bulk operations: ```javascript SvSyncScheduler.shared().pause(); // ... bulk model changes ... SvSyncScheduler.shared().resume(); // All pending syncs execute now ``` The app startup sequence uses this to prevent premature sync cycles during model and UI initialization. --- Source: /docs/Persistence/ # Persistence Local storage, cloud sync, and blob management for object graphs. - Local Object Pools - Cloud Object Pools - Local and Cloud Blob Storage --- Source: /docs/Persistence/Cloud%20Object%20Pools/ # Cloud Object Pools Cloud sync for object pools and collections using Firebase Storage. ## Overview Strvct's local persistence stores serialized object graphs in IndexedDB via `SvPersistentObjectPool`. Cloud sync extends this with two complementary strategies because different data has fundamentally different sync characteristics. A collection of independent items (e.g. characters or campaigns) benefits from per-item files — you can lazy-load, show thumbnails before downloading, and a change to one item doesn't require re-uploading everything. But an interconnected object graph (e.g. a game session with dozens of cross-referencing objects) can't be split into individual files because objects reference each other by ID — they must move as a unit. The two strategies reflect this distinction: - **Collection sync** — Individual items are synced as separate JSON files with a manifest. Items can be lazily loaded from stubs. - **Pool sync** — Entire object graphs are synced as a single pool. A write-ahead log of small delta files makes updates fast and efficient — only changed records are uploaded, with periodic compaction back to a full snapshot. Both strategies require the synced object graph to be **self-contained** — the rest of the system holds only a reference to the graph's root, and objects within the graph hold no persistent references to objects outside it. This isolation is what makes it possible to serialize, upload, and reconstruct the graph independently. Both strategies use Firebase Storage as the backend and are coordinated by `SvCloudSyncSource`. ## Collection Sync ### How It Works Each syncable collection maintains a folder in cloud storage at `/users/{userId}/{collectionName}/`. The folder contains one JSON file per item plus a manifest file that tracks ordering and metadata. **Manifest structure:** ```json { "subnodeIds": ["id1", "id2"], "items": { "id1": { "type": "MyItemClass", "title": "Item Title", "subtitle": "Item subtitle", "thumbnailUrl": "https://...", "lastModified": 1707123456789 } } } ``` The manifest enables lazy loading — the client can display item titles and thumbnails without downloading the full item data. Full item JSON is fetched on demand when the user navigates to an item. Collections can be nested. `SvCloudSyncSource` supports a `subPath` slot, allowing hierarchical storage structures like `/users/{userId}/{collectionName}/{itemId}/{subCollectionName}/`. ### Sync Flow **Push to cloud:** 1. Item serialized via `asCloudJson()` which calls `serializeToJson("Cloud", [])` 2. JSON uploaded to `/users/{userId}/{collectionName}/{itemId}.json` 3. Manifest updated with item metadata and re-uploaded **Pull from cloud:** 1. Manifest fetched to discover available items 2. Stubs created locally for each item (title, subtitle, thumbnail from manifest) 3. Full item data fetched lazily when accessed 4. Local item populated via `deserializeFromJson()` ### Conflict Resolution Collection sync uses a timestamp-based strategy: - Each item tracks `cloudLastModified` and `localLastModified` - If cloud is newer and local has no unsaved changes — update from cloud - If cloud is newer but local has unsaved changes — keep local (re-upload on next sync) - If local is newer — upload on next sync This is a "local wins" strategy — local changes always take priority. ## Pool Sync ### How It Works For complex object graphs where many interrelated objects need to be synced together, `SvSubObjectPool` serializes the entire pool as a single JSON document. The pool maps persistent unique IDs (puuids) to serialized object records: ```json { "{puuid1}": "{serialized_object_json}", "{puuid2}": "{serialized_object_json}" } ``` This is stored at `/users/{userId}/{collectionName}/{poolId}/pool.json`. ### Write Ahead Log To avoid uploading the entire pool on every save, `SvSubObjectPool` tracks changes since the last sync and produces incremental deltas: ```json { "timestamp": 1707123456789, "writes": { "{puuid1}": "{updated_object_json}", "{puuid3}": "{new_object_json}" }, "deletes": ["{puuid2}"] } ``` Deltas are stored as separate timestamped files alongside the main pool. On load, the client fetches `pool.json` and applies all deltas in order to reconstruct the current state. **Upload strategy:** - If changes affect less than 50% of records — upload a delta file - If changes exceed 50% — upload the full pool (more efficient than a large delta) - When delta count exceeds 20 — compact by uploading a full pool and deleting all deltas ### Concurrency Control Pool sync uses lock-based concurrency to prevent simultaneous edits: - `asyncAcquireOrRefreshLock()` acquires an exclusive lock before writing - Locks are refreshed periodically (every 60 seconds) during active sessions - `asyncReleaseLock()` releases the lock when the session ends - Lock acquisition and release are handled via cloud function endpoints ## Key Classes ### SvCloudSyncSource The primary cloud-aware sync class. Configured with a user ID, folder name, and Firebase Storage reference. Handles: - Item upload and download - Manifest management - Thumbnail uploads to content-addressed storage - Retry logic with exponential backoff - Orphaned file cleanup ### SvSubObjectPool An in-memory `SvObjectPool` (not backed by IndexedDB) designed for cloud sync. Provides: - `asyncSaveToCloud()` — saves with delta or full upload optimization - `collectDelta()` — produces incremental changes vs. last synced snapshot - `asyncCompactToCloud()` — consolidates deltas into a single pool file - `fromCloudJson(json)` — reconstructs the pool from cloud data - `asJson()` — serializes the complete pool for upload ### SvSyncCollectionSource Abstract base class for collection syncing. Defines the interface for: - `asyncSyncFromSource()` — pull from cloud - `asyncLazySyncFromSource()` — create stubs for lazy loading - `asyncSyncToSource()` — push to cloud ## Relationship to Local Persistence | Component | Backing Store | Cloud Sync | Purpose | |-----------|--------------|------------|---------| | `SvPersistentObjectPool` | IndexedDB | No | Local app state (singleton, never synced directly) | | `SvSubObjectPool` | In-memory | Yes | Session-level cloud sync with delta support | | `SvCloudSyncSource` | Firebase Storage | Yes | Collection-level cloud sync with manifests | The local `SvPersistentObjectPool` is the ground truth for the running application. Cloud sync operates alongside it — collections push individual items, while sessions create a `SvSubObjectPool` snapshot for upload. ## Auto-Sync Triggers Cloud sync is triggered automatically on: - Tab visibility change (switching back to the app) - Browser `beforeunload` event - Network online/offline transitions - Manual save actions --- Source: /docs/Persistence/Local%20and%20Cloud%20Blob%20Storage/ # Local and Cloud Blob Storage Content-addressable storage for binary data like images, audio, and video. ## Motivation The system addresses a fundamental challenge in web applications: **efficiently managing large binary data (images, audio, video) separately from structured object data**. **Key problems solved:** 1. **Synchronous vs Async APIs**: The main `SvObjectPool` persistence system uses synchronous operations for fast object loading. Binary blobs are large and require async I/O. Separating them prevents blocking. 2. **Deduplication**: Images or media might be referenced by multiple objects. Content-addressable storage (keyed by SHA-256 hash) means identical blobs are stored once. 3. **Lazy Loading**: Objects can be loaded quickly with just a hash reference. The actual blob data is fetched on-demand when needed. 4. **Cloud Sync**: `SvCloudBlobNode` extends local storage to support pushing/pulling blobs to Google Cloud Storage. ### Why Not Store Everything in Firebase Firestore? Firebase Firestore has constraints that make it unsuitable for storing the complete object graph directly: 1. **Document Size Limit**: Firestore enforces a **1MB maximum document size**. Objects that serialize themselves along with their contained children can easily exceed this limit (e.g., a campaign with locations, NPCs, treasures, and session history). 2. **No Graph Traversal**: If we fully decomposed storage to one document per object, retrieving an object graph would require **many sequential round trips**. Firestore has no support for traversing a directed graph of references in a single request - each document fetch is independent. 3. **Batching Limits**: Storing each object as its own document hits Firestore's batching limits (500 operations per batch). This makes bulk operations slow and would require additional synchronization mechanisms to ensure consistency across related documents. **The solution**: Store structured object data as larger, more self-contained documents locally (IndexedDB), and sync to cloud storage at a higher granularity. Binary blobs are separated out because they're the primary driver of document size, and content-addressable storage means they can be efficiently synced and deduplicated independently. --- ## Solution Summary The solution uses a **dual-database architecture** with hash-based references between them: | Component | Purpose | Storage | |-----------|---------|---------| | **SvObjectPool** | Stores structured objects (characters, sessions, etc.) | IndexedDB (sync API) | | **SvBlobPool** | Stores binary blobs (images, audio, video) | Separate IndexedDB (async API) | **The key insight**: Objects don't store blobs directly. Instead, they store a SHA-256 hash that acts as a pointer to the blob in `SvBlobPool`. This provides: - **Fast object loading** - Objects load synchronously with just a small hash string - **Lazy blob fetching** - Actual binary data loads asynchronously when needed - **Automatic deduplication** - Same content = same hash = stored once - **Simple GC** - Collect all referenced hashes from objects, delete everything else from blob storage **Three-layer class hierarchy**: 1. **SvBlobPool** - Low-level blob storage engine (store/retrieve by hash) 2. **SvBlobNode** - Node wrapper that manages a blob reference with lazy loading 3. **SvCloudBlobNode** - Adds cloud push/pull for cross-device sync Media-specific classes like **SvImageNode** and **SvVideoNode** extend `SvCloudBlobNode` to add format-specific behavior (MIME type handling, thumbnails, etc.) while inheriting all the storage and sync capabilities. --- ## SvBlobPool - The Blob Storage Engine **Location**: `strvct/source/library/node/storage/base/SvBlobPool.js` **Core concept**: A singleton that provides content-addressable blob storage using IndexedDB. ### Key Implementation Details 1. **Storage Format**: - `{sha256-hash}` → ArrayBuffer (the actual blob data) - `{sha256-hash}/meta` → JSON metadata (contentType, size, timeCreated, customMetadata) 2. **Deduplication**: When storing a blob, it computes the SHA-256 hash. If that hash already exists, the store is skipped. 3. **Weak Reference Cache**: Uses `SvEnumerableWeakMap` for `activeBlobs` - blobs in memory are cached by hash, but can be garbage collected when not in use. 4. **Concurrency Control**: Tracks active read/write operations via `activeReadsMap` and `activeWritesMap` to prevent duplicate concurrent operations on the same hash. 5. **Garbage Collection**: The `asyncCollectUnreferencedKeySet()` method removes blobs not in a provided set of referenced hashes. ### API ```javascript // Store - returns the SHA-256 hash const hash = await SvBlobPool.shared().asyncStoreBlob(blob, {customMeta: "value"}); // Retrieve const blob = await SvBlobPool.shared().asyncGetBlob(hash); // Check existence const exists = await SvBlobPool.shared().asyncHasBlob(hash); // Metadata only (without loading blob data) const meta = await SvBlobPool.shared().asyncGetMetadata(hash); ``` --- ## SvBlobNode - The Node Wrapper **Location**: `strvct/source/library/node/blobs/SvBlobNode.js` **Core concept**: A storable node that holds a reference to a blob via its hash, with lazy loading. ### Key Slots - `valueHash` (stored): The SHA-256 hash - persisted with the object - `blobValue` (transient): The actual Blob object - not persisted, loaded on demand ### Key Behaviors 1. **Lazy Loading**: `asyncBlobValue()` checks if the blob is loaded; if not, fetches it from `SvBlobPool` using the stored hash. 2. **Auto-hashing**: When `blobValue` is set, `didUpdateSlotBlobValue()` automatically computes the hash and writes to local storage. 3. **GC Integration**: `referencedBlobHashesSet()` returns the hash for garbage collection coordination with `SvObjectPool`. --- ## SvCloudBlobNode - Cloud Sync Extension **Location**: `strvct/source/library/node/blobs/SvCloudBlobNode.js` **Core concept**: Extends `SvBlobNode` with cloud storage push/pull capabilities. ### Additional Slots - `hasInCloud`: Boolean flag indicating cloud presence - `downloadUrl`: Public/private URL for cloud retrieval - `doesAutoSyncToCloud`: When true, automatically pushes to cloud after local storage ### Key Methods 1. **Push to Cloud** (`asyncPushToCloud`): - Takes local blob, uploads to cloud storage via `SvApp.shared().asyncPublicUrlForBlob()` - Sets `downloadUrl` and `hasInCloud` flag 2. **Pull from Cloud** (`asyncPullFromCloudByHash`): - Uses stored hash to fetch blob from cloud: `SvApp.shared().asyncBlobForHash(hash)` - Caches the retrieved blob locally 3. **Smart Resolution** (`asyncBlobValue`): - First checks in-memory blob - Then tries local storage - Finally falls back to cloud --- ## Data Flow Examples ### Storing an Image ``` User uploads image ↓ SvImageNode.asyncSetBlobValue(blob) ↓ SvBlobNode computes SHA-256 hash ↓ SvBlobPool.asyncStoreBlob() stores ArrayBuffer + metadata ↓ SvBlobNode stores only the hash in SvObjectPool ``` ### Loading an Image ``` Object loads from SvObjectPool (has valueHash: "abc123...") ↓ asyncBlobValue() called ↓ Check in-memory cache → miss ↓ SvBlobPool.asyncGetBlob("abc123...") → returns Blob ↓ Blob cached in activeBlobs for future access ``` --- ## Garbage Collection Coordination Since blobs and objects are stored in separate databases, garbage collection requires coordination between `SvObjectPool` and `SvBlobPool` to prevent orphaned blobs from accumulating. ### How It Works 1. **Objects report their blob references**: Any object that holds blob references implements `referencedBlobHashesSet()`, which returns a `Set` of SHA-256 hashes it depends on. 2. **SvObjectPool aggregates all references**: When GC runs, `SvObjectPool.allBlobHashesSet()` iterates through all stored objects, calling `referencedBlobHashesSet()` on each, and combines them into a master set of referenced hashes. 3. **BlobPool removes orphans**: `SvObjectPool.asyncCollectBlobs()` passes this master set to `SvBlobPool.asyncCollectUnreferencedKeySet()`, which: - Gets all keys currently in blob storage - Computes the difference (keys in storage but not in the reference set) - Deletes those orphaned blobs and their metadata ### Code Flow ``` SvObjectPool.asyncCollectBlobs() ↓ SvObjectPool.allBlobHashesSet() ↓ ┌───────────────────────────────────────┐ │ for each object in allObjects(): │ │ if object.referencedBlobHashesSet: │ │ hashesSet.addAll(object.referencedBlobHashesSet()) │ └───────────────────────────────────────┘ ↓ SvBlobPool.asyncCollectUnreferencedKeySet(hashesSet) ↓ ┌───────────────────────────────────────┐ │ allKeys = await idb.promiseAllKeys() │ │ orphans = allKeys.difference(hashesSet) │ │ await idb.promiseRemoveKeySet(orphans) │ └───────────────────────────────────────┘ ``` ### Implementation Details **In SvObjectPool** (`SvObjectPool.js`): ```javascript allBlobHashesSet () { const hashesSet = new Set(); this.allObjects().forEach(obj => { if (obj.referencedBlobHashesSet) { const objBlobHashes = obj.referencedBlobHashesSet(); hashesSet.addAll(objBlobHashes); } }); return hashesSet; } async asyncCollectBlobs () { const keySet = this.allBlobHashesSet(); const removedCount = await this.blobPool().asyncCollectUnreferencedKeySet(keySet); return removedCount; } ``` **In SvBlobNode** (`SvBlobNode.js`): ```javascript referencedBlobHashesSet () { const hashesSet = new Set(); const hash = this.valueHash(); if (hash) { hashesSet.add(hash); } return hashesSet; } ``` ### Key Points - **Mark-and-sweep style**: The approach mirrors classic GC - mark what's referenced, sweep the rest - **Async-safe**: Blob GC is fully async and doesn't block the synchronous SvObjectPool operations - **Metadata cleanup**: When a blob is removed, its `/meta` key is also deleted - **Deduplication preserved**: If multiple objects reference the same hash, the blob stays until all references are gone --- ## Design Strengths 1. **Separation of concerns**: Objects remain lightweight; blobs handled separately 2. **Efficient caching**: Content-addressable means natural deduplication 3. **Progressive loading**: UI can show objects immediately, load media lazily 4. **Cloud-ready**: Clean extension point for cloud sync without changing base storage 5. **GC-aware**: Hash-based references allow proper garbage collection across dual storage systems --- Source: /docs/Persistence/Local%20Object%20Pools/ # Local Object Pools IndexedDB-backed persistence for object graphs using dirty tracking and automatic serialization. ## Overview Strvct's persistence system stores object graphs in the browser's IndexedDB. Rather than requiring explicit save/load calls, it monitors slot changes and automatically commits dirty objects at the end of the event loop. The system is built from three layers: - **`SvObjectPool`** — Manages an in-memory cache of objects indexed by persistent unique IDs (puuids). Tracks dirty objects and handles serialization, deserialization, and garbage collection. - **`SvPersistentAtomicMap`** — An IndexedDB wrapper that loads the entire database into memory on open, provides synchronous read/write to the cache, and batches writes into atomic IndexedDB transactions on commit. - **`SvStorableNode`** — A node base class that hooks slot changes into the dirty tracking system. ## Opting Into Persistence ### Marking a Node as Storable A node class opts into persistence by calling `setShouldStore(true)` in its `initPrototype()` method: ```javascript (class MyNode extends SvStorableNode { initPrototypeSlots () { { const slot = this.newSlot("name", ""); slot.setSlotType("String"); slot.setShouldStoreSlot(true); } { const slot = this.newSlot("score", 0); slot.setSlotType("Number"); slot.setShouldStoreSlot(true); } { const slot = this.newSlot("cachedResult", null); // Not stored — transient, recomputed at runtime } } initPrototype () { this.setShouldStore(true); this.setShouldStoreSubnodes(true); } }.initThisClass()); ``` ### What Gets Stored Two flags control what is persisted: - **`setShouldStore(true)`** on the node — enables persistence for the node itself. Without this, the node and all its slots are ignored by the storage system. - **`setShouldStoreSlot(true)`** on individual slots — marks that slot's value for inclusion in the serialized record. Slots without this flag are transient and will be lost on reload. A third flag controls whether child nodes are stored: - **`setShouldStoreSubnodes(true)`** — persists the node's `subnodes` array. Used for collection nodes where children are the primary data. When false, subnodes are transient and must be recreated on load (typically via `setFinalInitProto()` on slots with `setIsSubnodeField(true)`). ### Inheritance Storage capability begins at `SvStorableNode` in the class hierarchy: ``` ProtoClass → SvNode → SvTitledNode → SvInspectableNode → SvViewableNode → SvStyledNode → SvStorableNode ``` Classes above `SvStorableNode` cannot be stored. Application model classes typically extend `SvStorableNode` or one of its subclasses like `SvSummaryNode`. ## Persistent Unique IDs Every stored object has a **puuid** — a 10-character random string (A-Za-z0-9) that serves as its key in the object pool. Puuids are generated on first access via `crypto.getRandomValues()` and stored as a non-enumerable `_puuid` property on the object. Puuids serve two purposes: 1. **Storage key** — the puuid is the key under which the object's serialized record is stored in IndexedDB. 2. **Object references** — when one stored object references another, the reference is serialized as `{ "*": "puuid" }` rather than inlining the referenced object. This allows the storage system to trace the object graph for garbage collection. ## Serialization Format Each stored object is serialized as a JSON record with a type field and an entries array: ```json { "type": "MyNode", "entries": [ ["name", "Alice"], ["score", 42], ["inventory", { "*": "aB3xK9mQ2p" }] ] } ``` Primitive values (strings, numbers, booleans, null) are stored inline. Object references are stored as `{ "*": "puuid" }` pointers. The `type` field identifies the class so the correct constructor can be used on deserialization. ### Serialization Methods - **`recordForStore(aStore)`** — iterates all slots with `shouldStoreSlot() == true`, calls `aStore.refValue()` on each value to handle object references, and returns the record. - **`loadFromRecord(aRecord, aStore)`** — iterates the record's entries, calls `aStore.unrefValue()` to resolve references back to live objects, and sets each slot value. ## Dirty Tracking and Automatic Commits The persistence system uses automatic dirty tracking so application code never needs to call save explicitly. ### The Mutation Flow 1. A slot value changes (via its setter method). 2. `SvStorableNode.didUpdateSlot()` checks whether the node and slot are both marked for storage. 3. If so, it calls `didMutate()`, which notifies the `SvObjectPool` via a mutation observer. 4. The pool calls `addDirtyObject(obj)` to add the object to its dirty set. 5. `addDirtyObject()` calls `scheduleStore()`, which uses `SvSyncScheduler` to defer the commit. 6. At the end of the event loop, `commitStoreDirtyObjects()` runs, serializing all dirty objects and writing them to IndexedDB in a single atomic transaction. Because the commit is deferred, multiple slot changes within the same event loop — even across different objects — are batched into a single IndexedDB transaction. This keeps persistence efficient even when many properties change in rapid succession. ### Commit Details `commitStoreDirtyObjects()` begins an IndexedDB transaction via `SvPersistentAtomicMap`, then iterates the dirty set. For each dirty object, it calls `recordForStore()` to serialize it and writes the JSON string to the map keyed by puuid. After all objects are stored, the transaction is committed atomically. If any object becomes dirty again during the commit (because serialization triggers side effects), the cycle repeats until the dirty set is empty. ## SvPersistentAtomicMap `SvPersistentAtomicMap` wraps `SvIndexedDbFolder` with a synchronous in-memory cache. On open, it loads the entire database into a JavaScript Map. All reads are served from memory. Writes go to an in-memory write cache and are flushed to the underlying store in batched transactions on commit. `SvIndexedDbFolder` is the environment abstraction point — it uses native IndexedDB in the browser and LevelDB (via `classic-level`) in Node.js, with the same async API in both cases. See [Headless Execution](../../Lifecycle/Headless%20Execution/index.html) for details on the storage abstraction. This design means: - **Reads are synchronous and fast** — no async IndexedDB calls during normal operation. - **Writes are batched** — many changes are committed in a single IndexedDB transaction. - **The full dataset is in memory** — suitable for object graphs that fit in browser memory (typically tens of megabytes). ### Transaction Model 1. `promiseBegin()` — snapshots the current state into a write cache. 2. Synchronous operations (`atPut`, `removeKey`) modify the write cache. 3. `promiseCommit()` — applies all changes to IndexedDB in a single transaction. ## Opening the Pool The application opens the pool once at startup: ```javascript const pool = SvPersistentObjectPool.sharedPool(); await pool.promiseOpen(); const root = await pool.rootOrIfAbsentFromClosure(() => { return MyRootNode.clone(); }); ``` `promiseOpen()` opens the IndexedDB database, loads all records into memory, and runs garbage collection. `rootOrIfAbsentFromClosure()` loads the existing root object from storage, or creates a new one using the provided closure if no root exists yet. ### Loading Cascade Loading the root object triggers a cascade: as each object is deserialized, its slot values that contain `{ "*": "puuid" }` references cause those referenced objects to be loaded in turn. After all objects in the reachable graph have been loaded, `finalInit()` is called on each to re-establish object relationships, followed by `afterInit()`. ## Class Name Migrations Renaming a stored class would normally break deserialization: existing IndexedDB records still reference the old class name in their `type` field, and `classForName()` would fail to find the renamed class. To avoid a forced data migration, the pool supports a **conversion map** that routes old class names to their new names at load time. ### Registering Conversions Before opening the pool, register the old→new mappings: ```javascript const store = this.defaultStore(); store.addClassNameConversion("OldName", "NewName"); // or for many at once: store.addClassNameConversionTuples([ ["OldName", "NewName"], ["AnotherOldName", "AnotherNewName"] ]); await store.promiseOpen(); ``` `classForName()` checks the map first — if an entry exists, it looks up the new name instead. Stored records are left unchanged on disk; next time each object is saved, it's written with the new class name, so the database gradually migrates itself. ### When to Use It - **Any rename of a stored class** — classes that `extend SvStorableNode` (directly or indirectly) have their names embedded in records, so a rename without a conversion mapping will strand that data. - **Permanent retention** — keep every mapping forever unless you're certain no user has stale data. Removing an entry later breaks any client whose IndexedDB predates the rename. ### Scope The conversion map only affects record deserialization. It does **not** rewrite code, JSDoc, or string literals elsewhere in the codebase — those must be updated directly (see the codemod pattern in `ClassRenames.json`). ## Garbage Collection The pool uses mark-and-sweep garbage collection to remove unreachable objects: 1. **Mark** — starting from the root object's puuid, recursively walk all `{ "*": "puuid" }` references in stored records, marking each visited puuid. 2. **Sweep** — delete any stored records whose puuids were not marked. Garbage collection runs automatically when the pool opens. It ensures that objects which are no longer reachable from the root — for example, nodes removed from a collection — are cleaned up from IndexedDB. Blob garbage collection runs separately via `SvBlobPool` (see [Local and Cloud Blob Storage](../Local%20and%20Cloud%20Blob%20Storage/index.html)). ## SvSubObjectPool `SvSubObjectPool` is an in-memory variant of `SvObjectPool` used for cloud sync rather than local persistence. It uses a plain `SvAtomicMap` instead of `SvPersistentAtomicMap` (no IndexedDB) and does not auto-schedule commits. Instead, it provides explicit methods for cloud upload with delta optimization. See [Cloud Object Pools](../Cloud%20Object%20Pools/index.html) for details. ## Key Classes Summary | Class | Purpose | |-------|---------| | `SvObjectPool` | Base pool: object cache, dirty tracking, serialization, GC | | `SvPersistentObjectPool` | Singleton `SvObjectPool` backed by IndexedDB | | `SvPersistentAtomicMap` | IndexedDB wrapper with synchronous in-memory cache | | `SvStorableNode` | Node base class that hooks slot changes to dirty tracking | | `SvSubObjectPool` | In-memory pool for cloud sync (no IndexedDB) | --- Source: /docs/Programming%20Idioms/ # Programming Idioms Recurring patterns and conventions for writing STRVCT code. - Style Guide - Async Patterns - Categories - Protocols --- Source: /docs/Programming%20Idioms/Async%20Patterns/ # Async Patterns Extended promises, serial and parallel iteration, and concurrency control. ## Promise.clone() STRVCT extends the native `Promise` class (via `Promise_ideal.js`) with a factory method that exposes the resolve and reject functions as callable methods on the promise itself. This is the foundation for most async coordination in the framework. ```javascript const promise = Promise.clone(); promise.setLabel("loadUserData"); // Later, from any code that holds a reference: promise.callResolveFunc(result); // or: promise.callRejectFunc(error); ``` ### Why not `new Promise()`? The native constructor captures resolve/reject inside the executor callback, making them inaccessible outside it. `Promise.clone()` stores them as properties so the promise can be resolved externally — by a different method, a timer, or a network callback — without nesting all the logic inside the constructor. ### Status tracking Cloned promises track their state explicitly: ```javascript promise.isPending() // true until resolved or rejected promise.isResolved() // true after callResolveFunc() promise.isRejected() // true after callRejectFunc() promise.isCompleted() // true if resolved or rejected ``` Calling `callResolveFunc()` or `callRejectFunc()` on an already-completed promise throws an assertion error, catching double-resolution bugs immediately. ### Timeouts ```javascript const promise = Promise.clone(); promise.setLabel("apiCall"); promise.beginTimeout(5000); // reject after 5 seconds if still pending ``` The timeout auto-cancels on resolution or rejection. The rejection message includes the label and duration for debugging. ### Await hooks ```javascript const promise = Promise.clone(); promise.setOnAwaitFunc((p) => { // Called the first time someone awaits this promise. // Useful for lazy initialization — don't start the work // until someone actually needs the result. startExpensiveOperation(p); }); ``` The framework also tracks `hasAwaiters()` — whether anyone is currently waiting on the promise. ### Common pattern: deferred resolution The most frequent use of `Promise.clone()` is to bridge callback-style or event-driven code into async/await: ```javascript async fetchData () { const promise = Promise.clone(); promise.setLabel("fetchData"); promise.beginTimeout(10000); this.setOnResponseCallback((data) => { promise.callResolveFunc(data); }); this.setOnErrorCallback((error) => { promise.callRejectFunc(error); }); this.sendRequest(); return promise; } ``` ## Array async iteration `Array_promises.js` adds methods for controlled async iteration. These are categories on `Array`, so they're available on any array. ### Serial iteration ```javascript await items.promiseSerialForEach(async (item, index) => { await processItem(item); }); ``` Processes one item at a time, in order. Throws immediately if any iteration fails. ### Serial with yielding ```javascript await items.promiseSerialTimeoutsForEach(async (item, index, total) => { await processItem(item); }, 0); // delay in ms between iterations ``` Like serial, but inserts a `setTimeout` between iterations. This yields control back to the event loop between items, preventing UI freezes during long-running sequences. A delay of `0` still yields; higher values add intentional pacing. ### Parallel ```javascript const results = await items.promiseParallelMap(async (item) => { return await transform(item); }); // or when you don't need results: await items.promiseParallelForEach(async (item) => { await processItem(item); }); ``` All items processed concurrently via `Promise.all()`. Fast, but can overwhelm APIs or exhaust resources if the array is large. ### Concurrency-limited batching ```javascript await items.promiseConcurrentSerialTimeoutsForEach( async (item, index, total) => { await callApi(item); }, 3, // maxConcurrent — at most 3 in flight at once 100, // delay in ms between batches async (error, item, index) => { // optional error handler — log and continue console.error("Failed:", item, error); } ); ``` The workhorse for API-heavy operations. Runs up to `maxConcurrent` items simultaneously, yields between batches, and supports per-item error handling without aborting the whole sequence. Uses `Promise.clone()` internally for coordination. ## Choosing the right iteration method | Method | Concurrency | Yielding | Error handling | Use when | |--------|------------|----------|----------------|----------| | `promiseSerialForEach` | 1 | No | Fail-fast | Order matters, small arrays | | `promiseSerialTimeoutsForEach` | 1 | Yes | Fail-fast | Order matters, UI must stay responsive | | `promiseParallelMap` | All | No | Fail-fast | Independent items, bounded array size | | `promiseConcurrentSerialTimeoutsForEach` | N | Yes | Configurable | API calls, large arrays, rate limiting | --- Source: /docs/Programming%20Idioms/Categories/ # Categories Extending existing classes by adding methods from separate files. ## Overview Categories allow you to add methods to an existing class without modifying its original file. This is borrowed from Objective-C/Smalltalk traditions and is used throughout STRVCT to separate concerns — keeping core class files focused on essential functionality while adding specialized behavior in dedicated files. A category is a class that extends the target class but calls `initThisCategory()` instead of `initThisClass()`. The framework copies the category's methods onto the target class's prototype rather than creating a new class in the hierarchy. ## Defining a category ```javascript // SvJsonGroup_patches.js — adds JSON Patch support to SvJsonGroup (class SvJsonGroup_patches extends SvJsonGroup { applyJsonPatches (patches) { // patch implementation } applyPatch (operation) { // single patch operation } }.initThisCategory()); ``` The key difference from a normal class: `.initThisCategory()` instead of `.initThisClass()`. This tells the framework to merge the methods into the parent class rather than registering a new class. ## Naming convention Category files use an underscore to separate the base class name from the category purpose: ``` SvJsonGroup.js // base class SvJsonGroup_patches.js // JSON Patch operations SvJsonGroup_clientState.js // client state tool methods ``` The category class name follows the same pattern: `SvJsonGroup_patches`, `Array_promises`, `Promise_ideal`. ## Loading order Base classes must be loaded before their categories. In `_imports.json` files, list the base class first: ```json [ "SvJsonGroup.js", "SvJsonGroup_patches.js", "SvJsonGroup_clientState.js" ] ``` If a category loads before its base class, the `extends` clause will fail because the base class doesn't exist yet. ## Extending JavaScript builtins Categories are commonly used to extend native JavaScript classes. The boot system includes several: ```javascript // Promise_ideal.js — adds clone(), status tracking, timeouts (class Promise_ideal extends Promise { static clone () { /* ... */ } callResolveFunc (...args) { /* ... */ } isPending () { /* ... */ } }.initThisCategory()); // Array_promises.js — adds async iteration methods (class Array_promises extends Array { async promiseSerialForEach (aBlock) { /* ... */ } async promiseParallelMap (aBlock) { /* ... */ } }.initThisCategory()); ``` After these categories load, every `Promise` and `Array` instance has the new methods — no imports needed. ## When to use categories **Good uses:** - Separating a large class into thematic files (e.g., persistence, UI, serialization) - Adding framework methods to JavaScript builtins - Adding app-specific behavior to framework classes without forking them - Keeping a base class stable while iterating on extensions **Avoid when:** - The methods need their own slots or state — categories can't add slots, only methods - You need polymorphism (override behavior in subclasses) — a subclass is the right tool - The base class is in an external library that doesn't use the STRVCT class system ## How it works internally `initThisCategory()` iterates over the category class's own methods (both prototype and static) and copies them onto the target class. It does not create a new entry in the class registry — `SvJsonGroup_patches` is not a class you can instantiate or look up by name. It's purely a delivery mechanism for methods. This means: - `instanceof` checks are unaffected — an `SvJsonGroup` instance won't show as `instanceof SvJsonGroup_patches` - Category methods have full access to `this` and the base class's slots and methods - If two categories define the same method name on the same base class, the last one loaded wins (with a warning) ## Related patterns - [Slot Patterns](../Slot%20Patterns/index.html) — how to declare properties on classes - [Lifecycle](../../Lifecycle/index.html) — `initPrototypeSlots` / `initPrototype` and the initialization chain, which categories cannot participate in (only methods are merged) --- Source: /docs/Programming%20Idioms/Protocols/ # Protocols Declaring interfaces and verifying conformance at runtime. ## Overview Protocols are STRVCT's interface system. A protocol defines a set of methods that a class promises to implement. Unlike duck-typing (where you check for individual methods at call sites), protocols give the contract a name, make it inheritable, and verify it at registration time rather than at the point of use. The most important property of the protocol system is **fail-fast verification**: when a class declares that it conforms to a protocol, the framework immediately checks that all required methods are present. **If any are missing, an error is thrown at startup** -- not later when a rarely-exercised code path happens to call the missing method. This moves an entire category of bugs from runtime surprises to immediate, deterministic failures. The conformance check is also designed to be very fast. It uses a single set-subset operation -- each required method name is looked up in the class's `Set` of slot names in O(1). Since this runs once at startup during class registration, there is zero per-call overhead. The cost is paid once, upfront, and the guarantee holds for the lifetime of the application. The system is inspired by Objective-C's `@protocol` and Smalltalk's message categories. ## Defining a protocol A protocol is a subclass of `Protocol` that lists the required methods as empty instance methods: ```javascript (class SvAudioClipProtocol extends Protocol { play () { } addDelegate (audioClipDelegate) { } removeDelegate (audioClipDelegate) { } stop () { } }.initThisProtocol()); ``` Key points: - Call `.initThisProtocol()` instead of `.initThisClass()`. This registers the protocol and asserts the naming convention. - The class name **must** end with `Protocol` (enforced at runtime). - Method bodies are empty -- they exist to declare the interface, not to provide default implementations. - Use the `@interface` JSDoc tag (not `@class`) in the file's module comment. ## Implementing a protocol A class declares conformance by calling `addProtocol()` in its `initPrototype()` method: ```javascript (class SvWaSound extends SvSummaryNode { initPrototype () { this.addProtocol(SvAudioClipProtocol); } play () { // actual implementation } addDelegate (audioClipDelegate) { // actual implementation } removeDelegate (audioClipDelegate) { // actual implementation } stop () { // actual implementation } }.initThisClass()); ``` When `addProtocol()` is called, the framework immediately checks that the class's methods satisfy the protocol. If any required method is missing, an error is thrown at class-registration time -- not later when the method would have been called. ## Verification API The protocol system provides several methods on `ProtoClass` (available to all STRVCT classes): | Method | Purpose | |---|---| | `addProtocol(protocol)` | Declare conformance; throws if methods are missing | | `conformsToProtocol(protocol)` | Returns `true` if the class has registered the protocol | | `assertConformsToProtocol(protocol)` | Throws if the class doesn't conform | | `methodsConformToProtocol(protocol)` | Returns which methods satisfy the protocol | | `implementsMethodNamesSet(set)` | Checks if the class implements a given set of method names | On the `Protocol` class itself: | Method | Purpose | |---|---| | `Protocol.allProtocols()` | Returns all registered protocol subclasses | | `protocol.addImplementer(class)` | Records a class as an implementer (called automatically by `addProtocol`) | | `protocol.implementers()` | Returns the set of all classes that conform to this protocol | ## Protocol inheritance Protocols are classes, so they support inheritance. A protocol can extend another protocol to build larger interfaces from smaller ones: ```javascript (class SvExtendedAudioProtocol extends SvAudioClipProtocol { seek (position) { } duration () { } }.initThisProtocol()); ``` A class that conforms to `SvExtendedAudioProtocol` must implement both its own methods and those inherited from `SvAudioClipProtocol`. ## Existing protocols The framework currently declares four formal protocols: - **`SvAudioClipProtocol`** -- playback interface: `play()`, `stop()`, `addDelegate()`, `removeDelegate()` - **`SvAudioClipDelegateProtocol`** -- callback interface: `onSoundEnded(audioClip)` - **`SvDragSourceProtocol`** -- drag source callbacks: `onDragSourceBegin()`, `onDragSourceDropped()`, `onDragSourceEnd()`, `acceptsDropHover()`, and others - **`SvDragDestinationProtocol`** -- drop target callbacks: `onDragDestinationEnter()`, `onDragDestinationHover()`, `onDragDestinationExit()`, `acceptsDropHoverComplete()`, `acceptsDrop()`, and others ## Protocols vs. duck-typing Many parts of the codebase use informal duck-typing -- checking for a method's existence before calling it: ```javascript if (node && node.nodeAcceptsDrop) { node.nodeAcceptsDrop(droppedNode); } ``` This works, but it has drawbacks: - The interface contract is implicit -- you have to read call sites to discover what methods are expected. - There's no early verification -- a missing method surfaces only when the code path is hit at runtime. - There's no way to query which classes implement the interface. Formal protocols address all three. The trade-off is a small amount of ceremony (defining the protocol class and calling `addProtocol`). For optional interfaces where not every class is expected to conform, duck-typing remains appropriate. For interfaces that represent a real contract between components, a protocol is the better choice. ## Naming conventions - Protocol class names always end with `Protocol`: `SvAudioClipProtocol`, `SvDragSourceProtocol` - Protocol files live alongside the classes they relate to (e.g., the audio protocols are in `library/node/audio/`) - Delegate protocols follow the pattern `SvThingDelegateProtocol` for callback interfaces ## Related patterns - [Categories](../Categories/index.html) -- extending classes with methods from separate files (protocols define *what* methods to implement; categories *add* methods) - [Complete Protocols](../../Future%20Work/Complete%20Protocols/index.html) -- future work on formalizing the codebase's many undeclared protocols --- Source: /docs/Programming%20Idioms/Style%20Guide/ # Style Guide Naming conventions, formatting rules, and code structure patterns. ## Classes Class names use UpperCamelCase with a two-letter prefix indicating their origin: - **`Sv`** -- framework classes: `SvNode`, `SvStorableNode`, `SvJsonGroup`, `SvNotificationCenter` - **Application prefix** -- applications built on STRVCT should choose their own short prefix and use it consistently for all custom classes. The prefix doesn't need to be two characters -- any short, distinctive prefix works (`Uo`, `App`, `Xyz`). This prevents name collisions between framework, application, and third-party code. The prefix is not applied to external libraries or JavaScript builtins extended via categories. Acronyms are treated as a single capitalized unit: `SvJsonGroup` (not `SvJSONGroup`), `SvAiService`, `SvHttpResponseCodes`, `SvDbTable`. **View classes** append `View` to the model class name. The framework uses this convention to automatically discover the view for a given node: | Model class | View class | |---|---| | `SvNode` | `SvNodeView` | | `SvField` | `SvFieldView` | Classes use the self-initializing pattern -- the class expression is wrapped in parentheses and `.initThisClass()` is called inline: ```javascript (class SvTimeFormatter extends SvNode { // ... }.initThisClass()); ``` This registers the class with the framework immediately upon evaluation. ## Slots Slot names use lowerCamelCase. The slot system automatically generates a getter and setter from the name: | Slot declaration | Getter | Setter | |---|---|---| | `newSlot("userName", "")` | `userName()` | `setUserName(value)` | | `newSlot("is24Hour", false)` | `is24Hour()` | `setIs24Hour(value)` | | `newSlot("subnodes", null)` | `subnodes()` | `setSubnodes(value)` | Each slot declaration is wrapped in a block scope so `const slot` can be reused without naming collisions: ```javascript initPrototypeSlots () { { const slot = this.newSlot("userName", ""); slot.setSlotType("String"); } { const slot = this.newSlot("isActive", false); slot.setSlotType("Boolean"); } } ``` ### Boolean slots Boolean slot names use a query-style prefix that reads naturally as a question: | Prefix | Usage | Examples | |---|---|---| | `is` | Identity or state | `isComplete`, `isEditable`, `isLoggedIn` | | `has` | Possession or presence | `hasShared`, `hasSelection` | | `can` | Ability or permission | `canDelete`, `canReorderSubnodes`, `canInspect` | | `should` | Configuration flags | `shouldStore`, `shouldStoreSlot`, `shouldStoreSubnodes` | | `does` | Behavioral switches | `doesPadHours`, `doesHookSetter` | | `shows` | Visibility toggles | `showsMeridiem`, `showsHours` | ## Instance Variables Instance variables use an underscore prefix (`_userName`, `_isActive`) and are subject to three rules: ### 1. Always declare via `newSlot()` Never assign an instance variable directly. All instance variables are created by the slot system in `initPrototypeSlots()`: ```javascript // Correct { const slot = this.newSlot("userName", ""); slot.setSlotType("String"); } // Wrong -- bypasses the slot system entirely this._userName = ""; ``` Declaring variables through `newSlot()` ensures they participate in the framework's infrastructure: getter/setter generation, dirty tracking, persistence, view synchronization, JSON Schema, and ARIA metadata. A manually assigned `_` variable gets none of this. ### 2. Internal access: use the getter Within the same object, access instance variables through the generated getter (`this.userName()`), not directly (`this._userName`). The getter is the standard access path; direct access is reserved for rare, performance-critical cases where you intentionally need to skip hooks. ```javascript // Standard -- uses the getter formattedName () { return this.userName().toUpperCase(); } // Avoid -- skips hooks, breaks the uniform access pattern formattedName () { return this._userName.toUpperCase(); } ``` Bypassing the getter may seem harmless for reads, but it creates a maintenance hazard: if a subclass or category overrides the getter (to add lazy initialization, computed values, or delegation), direct `_` access silently bypasses that override. ### 3. External access: always use the getter Accessing another object's instance variables directly (`other._userName`) is never acceptable. External code must always go through the public getter (`other.userName()`). This isn't just convention -- the setter performs dirty tracking for persistence, posts `didUpdateSlot` notifications, and schedules view sync. A direct `other._name = x` silently breaks storage, UI updates, and any observers watching that slot. ```javascript // Correct player.setUserName("Alice"); const name = player.userName(); // Wrong -- breaks persistence, notifications, and view sync player._userName = "Alice"; const name = player._userName; ``` ## Methods Methods use lowerCamelCase. There is no `get` prefix -- a bare noun is the getter. This follows the Uniform Access Principle: `node.name()` and `node.formattedName()` look identical at call sites, so callers don't know or care whether a value is stored or computed. The naming doesn't leak implementation details into the API. ### Short methods Methods should do one thing. If a method is growing long, extract named helper methods -- even if each helper is only called once. Small methods with descriptive names are easier to read, override, and debug than large methods with inline comments explaining each section. ### Method chaining Setters and configuration methods return `this` to support chaining: ```javascript slot.setSlotType("String").setShouldStoreSlot(true).setSyncsToView(true); ``` All `init` methods (`init()`, `finalInit()`, `afterInit()`) should also return `this`. ### Getters and setters - **Getter**: `propertyName()` -- bare name, no prefix - **Setter**: `setPropertyName(value)` -- `set` prefix, UpperCamelCase property name - **Computed getters**: descriptive name for the derived value: `formattedValue()`, `visibleClassName()`, `hoursString()` Do **not** use ES6 `get`/`set` property definitions. Beyond conflicting with the slot system, ES6 getters make property access and method calls syntactically indistinguishable -- `obj.name` could be a simple read or an expensive computation. With `obj.name()`, the parentheses consistently signal "this is a method call", which matters in a framework where slot access triggers hooks. ### Lifecycle methods The initialization chain uses reserved names in a fixed order: 1. `initPrototypeSlots()` -- declare slots 2. `initPrototype()` -- configure class-wide behavior 3. `init()` -- basic instance setup 4. `finalInit()` -- complex initialization, object relationships 5. `afterInit()` -- post-initialization tasks `initPrototypeSlots()` and `initPrototype()` should **never** call `super` -- the framework walks the class hierarchy automatically, calling each level in base-to-derived order. Adding `super` would cause each level to execute multiple times. The other init methods (`init()`, `finalInit()`, `afterInit()`) **should** call `super`. ### Event methods Three prefix conventions distinguish when and how events are handled: | Prefix | Timing | Examples | |---|---|---| | `will` | Before something happens | `willRemoveSubnode()` | | `did` | After something happened | `didUpdateSlot()`, `didChangeSubnodeList()`, `didInit()` | | `on` | In response to an external event | `onDragSourceBegin()`, `onSoundEnded()`, `onBrowserDropChunk()` | `did` and `will` are typically used for internal lifecycle notifications. `on` is used for callbacks from other objects (delegates, gesture recognizers, external events). ### Async methods Async methods use the `async` keyword and an `async` prefix in the method name: ```javascript async asyncStoreBlob (blob) { ... } async asyncGetBlob (hash) { ... } async asyncCollectUnreferencedKeySet () { ... } ``` The prefix makes async operations searchable (`grep "asyncLoad"` finds all of them) and self-documenting at call sites -- `await node.asyncLoadChildren()` is immediately clear without checking the declaration. ### Factory methods - `clone()` -- standard instantiation (called on the class: `SvNode.clone()`) - `shared()` -- singleton access (called on the class: `SvNotificationCenter.shared()`) - `newSlot()`, `newSubnode()`, `newObservation()` -- create and return a child object owned by the receiver ## Categories Category files use an underscore to separate the base class name from the category purpose: ``` SvJsonGroup.js // base class SvJsonGroup_patches.js // JSON Patch operations SvJsonGroup_clientState.js // client state tool methods SvTile_dragging.js // drag behavior SvTile_keyboard.js // keyboard handling ``` The category class name matches the filename: `SvJsonGroup_patches`, `SvTile_dragging`. The underscore convention makes it easy to see at a glance which class is being extended and what the extension adds. See [Categories](../Categories/index.html) for details on how categories work. ## Protocols Protocol class names **must** end with `Protocol` (enforced at runtime by `initThisProtocol()`): ``` SvAudioClipProtocol SvAudioClipDelegateProtocol SvDragSourceProtocol SvDragDestinationProtocol ``` Delegate protocols follow the pattern `SvThingDelegateProtocol` -- naming the object that receives the callbacks, not the object that sends them. Protocol files follow the standard one-class-per-file rule and live alongside the classes they relate to, not in a central directory: ``` library/node/audio/SvAudioClipProtocol.js library/node/audio/SvAudioClipDelegateProtocol.js library/node/node_views/.../SvTilesView/SvDragSourceProtocol.js library/node/node_views/.../SvTilesView/SvDragDestinationProtocol.js ``` See [Protocols](../Protocols/index.html) for details on defining and implementing protocols. ## Files and directories **One class per file.** The filename matches the class name: `SvNode.js`, `SvTimeFormatter.js`. Category files follow the `ClassName_category.js` pattern. This makes finding a class's source trivial -- the filename is the class name -- and matches the `_imports.json` resource loading system's assumption of one declaration per file. **No import/require.** STRVCT uses its own resource loading system based on `_imports.json` files, not standard JavaScript modules. The CAM (Content-Addressable Memory) loader provides content-based caching, deduplication, and atomic updates that standard ES modules can't. Do not add `import` or `require` statements to framework code. **Directory names** are lowercase or lowerCamelCase, organized by function: | Directory | Purpose | |---|---| | `library/ideal/` | Base classes, formatters, utilities | | `library/node/` | Node hierarchy, storage, views | | `library/view/` | View layer, DOM abstractions | | `library/services/` | AI, cloud storage, media services | | `browser-only/` | Excluded in Node.js environments | | `server-only/` | Excluded in browser environments | ## Notifications Notification names follow the `did`/`will` pattern used by event methods. When stored as a slot for reuse, they use a `Note` suffix: ```javascript // Declaring a notification slot this.newSlot("didUpdateNodeNote", null); // Posting this.postNoteNamed("onRequestNavigateToNode", this); ``` ## Slot types The `setSlotType()` method takes a string matching either a JavaScript built-in or a STRVCT class name: - **Primitives**: `"String"`, `"Number"`, `"Boolean"`, `"Array"`, `"Date"` - **Framework types**: `"SvNode"`, `"SvNotification"`, `"SvSubnodesArray"` - **Semantic types**: `"Action"`, `"UUID"`, `"Integer"`, `"Float"` These are used for type checking, JSON Schema generation, and documentation -- not for runtime enforcement in most cases. ## Things to avoid - **ES6 `get`/`set` property definitions** -- use `foo()` / `setFoo()` instead. ES6 getters hide method calls behind property-access syntax, making it impossible to distinguish a simple read from a computation with side effects. - **`import` / `require`** -- use the `_imports.json` resource loading system. The CAM loader provides content-based caching and atomic updates that standard ES modules can't. - **`super` in `initPrototypeSlots` / `initPrototype`** -- the framework walks the hierarchy automatically. Adding `super` causes each level to execute multiple times. - **`instance.hasOwnProperty()`** -- use `Object.hasOwn(instance, key)` instead. `hasOwnProperty` is a prototype method that can be shadowed by an object's own property; `Object.hasOwn()` is a static method that can't be overridden. - **Plain objects as dictionaries** -- use `Map` for key-value collections. Plain objects risk prototype pollution (`toString`, `constructor` as key names), only support string keys, and lack `.size`. - **Direct instance variable access** -- see [Instance Variables](#instance-variables) for the full rules. In short: always use the getter. ## Formatting Style rules enforced by ESLint: - **Space before parentheses** in all function and method declarations: `initPrototype () {`, not `initPrototype() {`. This isn't just cosmetic -- it makes text search unambiguous: `methodName (` finds definitions, `methodName(` finds call sites. - **Four-space indentation**, no tabs. - **Semicolons required** at the end of statements. Avoids Automatic Semicolon Insertion edge cases, which matters more than usual in a codebase that loads code via `eval`. These apply to function declarations, expressions, async functions, and method definitions uniformly. --- Source: /docs/Reference/ # Reference Class hierarchy, module hierarchy, and protocol definitions. - Classes - Modules - Protocols --- Source: /docs/Reference/Classes/ # Classes Complete class inheritance hierarchy. - Object - [AjvValidator](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2FAjvValidator.js) - Array - [Array_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FArray_ideal.js) - [Array_promises](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fcategories%2FArray_promises.js) - [Array_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FArray_store.js) - [SvHookedArray](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcollections%2FSvHookedArray.js) - [SvIndexedArray](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcollections%2FSvIndexedArray.js) - [SvSortedArray](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcollections%2FSvSortedArray.js) - [SvSubnodesArray](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2FSvSubnodesArray.js) - ArrayBuffer - [ArrayBuffer_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FArrayBuffer_ideal.js) - [ArrayBuffer_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FArrayBuffer_store.js) - [Base](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FBase.js) - [BaseHttpsServer](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FBaseHttpsServer.js) - [BaseHttpsServerRequest](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FBaseHttpsServerRequest.js) - [AcmeChallengeRequest](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2Frequests%2FAcmeChallengeRequest.js) - [FileRequest](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2Frequests%2FFileRequest.js) - [CliWebServer](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FCliWebServer.js) - [SvMimeExtensions](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FSvMimeExtensions.js) - [SvBasicJsonRepairShop](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fjson%2FSvBasicJsonRepairShop.js) - [BigInt_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FBigInt_store.js) - Blob - [Blob_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FBlob_ideal.js) - Boolean - [Boolean_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FBoolean_ideal.js) - [Boolean_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2Fliterals%2FBoolean_store.js) - [ContentBase](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentBase.js) - [ContentCards](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentCards.js) - [ContentImage](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentImage.js) - [ContentKeyValue](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentKeyValue.js) - [ContentOrderedList](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentOrderedList.js) - [ContentTable](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentTable.js) - [ContentText](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentText.js) - [ContentTimeline](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentTimeline.js) - [ContentToc](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentToc.js) - [ContentUnorderedList](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentUnorderedList.js) - Date - [Date_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FDate_ideal.js) - [Date_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FDate_store.js) - [SvEnumerableWeakMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fweak%2FSvEnumerableWeakMap.js) - [SvEnumerableWeakSet](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fweak%2FSvEnumerableWeakSet.js) - Error - [SvAiRequestOverloadedError](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FRequests%2Ferrors%2FSvAiRequestOverloadedError.js) - [Error_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FError_ideal.js) - [Error_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FError_store.js) - [SvMissingSlotError](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_copying.js) - [o](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [a](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [s](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [SvJsonPatchError](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2Fpatches%2FSvJsonPatchError.js) - [FileReader](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FFileReaderShim.js) - [FontFace](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FFontFaceShim.js) - [HTMLCanvasElement](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FImageShim.js) - [HTMLElement_textField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2Fbrowser-only%2FHtmlElement_textField.js) - [i](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [c](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [f](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [h](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [S](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [v](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [y](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [_](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [b](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [E](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [$](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [g](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [k](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [N](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [P](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [m](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [p](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [u](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [Image](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FImageShim.js) - [Image_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FImage_ideal.js) - [Image_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FImage_store.js) - [ImportsIndexer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Findex-builder%2FImportsIndexer.js) - [j](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [JsClassParser](../../resources/class-doc/class_doc.html?path=%2Fdocs%2Fresources%2Fclass-doc%2Fclass_doc_parser.js) - [JsonRepairShop](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fjsonrepair%2FJsonRepairShop.js) - [l](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [d](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - Map - [SvFifoMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FSvFifoMap.js) - [SvHookedMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcollections%2FSvHookedMap.js) - [Map_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FMap_ideal.js) - [Map_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FMap_store.js) - [SvMarkdownRelative](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmarkdown%2FSvMarkdownRelative.js) - [SvMarkdownToc](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmarkdown%2FSvMarkdownToc.js) - [MinimalIndexedDbFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fserver-only%2Ftests%2FMinimalLevelDbWrapper.js) - [Mirror](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FMirror.js) - [n](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - Number - [Number_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FNumber_ideal.js) - [Number_random](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FNumber_random.js) - [Number_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2Fliterals%2FNumber_store.js) - [Object_boot](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fcategories%2FObject_boot.js) - [Object_categorySupport](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fcategories%2FObject_categorySupport.js) - [Object_class](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_class.js) - [Object_copying](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_copying.js) - [Object_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_ideal.js) - [Object_init](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_init.js) - [Object_mutation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_mutation.js) - [Object_notification](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_notification.js) - [Object_puuid](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_puuid.js) - [Object_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FObject_store.js) - [Object_timeouts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_timeouts.js) - [PageIndex](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FPageIndex.js) - [SvPersistentAtomicMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fstorage%2FSvPersistentAtomicMap.js) - [Promise_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fcategories%2FPromise_ideal.js) - [ProtoClass](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2FProtoClass.js) - [SvAtomicMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmap%2FSvAtomicMap.js) - [SvByteFormatter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fformatters%2FSvByteFormatter.js) - [SvCanvasTextTapeMeasure](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FHelpers%2FTapeMeasures%2FSvCanvasTextTapeMeasure.js) - [SvCssAnimation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FAttributes%2FSvCssAnimation.js) - [SvCssColor](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FHelpers%2FSvCssColor.js) - [SvDevice](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvDevice.js) - [SvGamePad](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvGamePad.js) - [SvKeyboardKey](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvKeyboardKey.js) - [SvMouse](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvMouse.js) - [SvKeyboard](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvKeyboard.js) - [SvTouchScreen](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvTouchScreen.js) - [SvDevices](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvDevices.js) - [SvDocumentation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmisc%2FSvDocumentation.js) - [SvDomBorderRadius](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FAttributes%2FSvDomBorderRadius.js) - [SvDomCssInspector](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FHelpers%2FSvDomCssInspector.js) - [SvDomTextTapeMeasure](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FHelpers%2FTapeMeasures%2FSvDomTextTapeMeasure.js) - [SvDomTransition](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FAttributes%2FSvDomTransition.js) - [SvDomTransitions](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FAttributes%2FSvDomTransitions.js) - [SvElementDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvElementDomView.js) - [SvCssDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvCssDomView.js) - [SvSubviewsDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvSubviewsDomView.js) - [SvListenerDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvListenerDomView.js) - [SvVisibleDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvVisibleDomView.js) - [SvGesturableDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvGesturableDomView.js) - [SvResponderDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvResponderDomView.js) - [SvControlDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvControlDomView.js) - [SvSelectableDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvSelectableDomView.js) - [SvEditableDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvEditableDomView.js) - [SvDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvDomView.js) - [SvDocumentBody](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvDocumentBody.js) - [SvDomView_animations](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvDomView_animations.js) - [SvDomView_browserDragAndDrop](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvDomView_browserDragAndDrop.js) - [SvDragBarView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvDragBarView.js) - [SvFlexDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvFlexDomView.js) - [SvButtonView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvButtonView.js) - [SvTileNoteButtonView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Fsubviews%2FSvTileNoteButtonView.js) - [SvCloseButton](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvCloseButton.js) - [SvStyledDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvStyledDomView.js) - [SvBooleanView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvBooleanView.js) - [SvDragView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvDragView.js) - [SvNodeView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvNodeView.js) - [SvNavView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvNavView.js) - [SvSceneView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpatial%2FSvSceneView.js) - [SvScrollContentView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvScrollContentView.js) - [SvTilesView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView.js) - [SvTilesView_dragViewProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_dragViewProtocol.js) - [SvTilesView_gestures](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_gestures.js) - [SvTilesView_helpers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_helpers.js) - [SvTilesView_keyboard](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_keyboard.js) - [SvTilesView_orientation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_orientation.js) - [SvTilesView_selection](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_selection.js) - [SvTilesView_styling](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_styling.js) - [SvStackView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvStackView.js) - [SvBrowserView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2FSvBrowserView.js) - [SvCoachableView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvCoachableView.js) - [SvImageView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvImageView.js) - [SvImageWellView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvImageWellView.js) - [SvVideoView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvVideoView.js) - [SvVideoWellView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvVideoWellView.js) - [SvTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile.js) - [SvBreadCrumbsTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvBreadCrumbsTile.js) - [SvActionFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvActionFieldTile.js) - [SvFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvFieldTile.js) - [SvSceneViewWellFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpatial%2FSvSceneViewWellFieldTile.js) - [SvBooleanFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvBooleanFieldTile.js) - [SvImageWellFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvImageWellFieldTile.js) - [SvStringFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvStringFieldTile.js) - [SvPasswordFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvPasswordFieldTile.js) - [SvTextAreaFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvTextAreaFieldTile.js) - [SvChatMessageTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvChatMessageTile.js) - [SvChatInputTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvChatInputTile.js) - [SvVideoWellFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvVideoWellFieldTile.js) - [SvTextNodeTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTextNodeTile.js) - [SvTile_dragging](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile_dragging.js) - [SvTile_gestures](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile_gestures.js) - [SvTile_keyboard](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile_keyboard.js) - [SvTile_slideGesture](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile_slideGesture.js) - [SvTile_styling](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile_styling.js) - [SvTitledTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTitledTile.js) - [SvHeaderTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvHeaderTile.js) - [SvFontTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffonts%2FSvFontTile.js) - [SvImageTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fimages%2FSvImageTile.js) - [SvOptionNodeTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2Foptions%2FSvOptionNodeTile.js) - [SvOptionsNodeTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2Foptions%2FSvOptionsNodeTile.js) - [SvPointerFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvPointerFieldTile.js) - [SvTileContainer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvTileContainer.js) - [SvTextView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvTextView.js) - [SvPasswordView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvPasswordView.js) - [SvTileNoteView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Fsubviews%2FSvTileNoteView.js) - [SvTileSubtitleView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Fsubviews%2FSvTileSubtitleView.js) - [SvTileTitleView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Fsubviews%2FSvTileTitleView.js) - [SvgIconView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvgIconView.js) - [SvScrollToBottomButton](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvScrollToBottomButton.js) - [SvScrollView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvScrollView.js) - [SvStackScrollView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvStackScrollView.js) - [SvCoachMarkView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2Fcoachmarks%2FSvCoachMarkView.js) - [SvOverlayBannerView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvOverlayBannerView.js) - [SvPanelView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvPanelView.js) - [SvScrimView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvScrimView.js) - [SvEventListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2FSvEventListener.js) - [SvEventManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2FSvEventManager.js) - [SvEventSetListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2FSvEventSetListener.js) - [SvAnimationListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvAnimationListener.js) - [SvBatteryListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvBatteryListener.js) - [SvClipboardListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvClipboardListener.js) - [SvDocumentListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvDocumentListener.js) - [SvDragListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvDragListener.js) - [SvDropListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvDropListener.js) - [SvFocusListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvFocusListener.js) - [SvGamePadListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvGamePadListener.js) - [SvKeyboardListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvKeyboardListener.js) - [SvMouseListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvMouseListener.js) - [SvMouseMoveListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvMouseMoveListener.js) - [SvScrollListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvScrollListener.js) - [SvSelectListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvSelectListener.js) - [SvSpeechListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvSpeechListener.js) - [SvTouchListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvTouchListener.js) - [SvTouchMoveListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvTouchMoveListener.js) - [SvTransitionListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvTransitionListener.js) - [SvWebSocketListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvWebSocketListener.js) - [SvWheelListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvWheelListener.js) - [SvWindowListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvWindowListener.js) - [SvFirebaseStoragePermissions](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FSvFirebaseStoragePermissions.js) - [SvGamePadManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvGamePadManager.js) - [SvGestureManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2FSvGestureManager.js) - [SvGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2FSvGestureRecognizer.js) - [SvLongPressGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvLongPressGestureRecognizer.js) - [SvOrientGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvOrientGestureRecognizer.js) - [SvPanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvPanGestureRecognizer.js) - [SvEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2FSvEdgePanGestureRecognizer.js) - [SvBottomEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fview%2FSvBottomEdgePanGestureRecognizer.js) - [SvLeftEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fview%2FSvLeftEdgePanGestureRecognizer.js) - [SvRightEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fview%2FSvRightEdgePanGestureRecognizer.js) - [SvTopEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fview%2FSvTopEdgePanGestureRecognizer.js) - [SvScreenEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fscreen%2FSvScreenEdgePanGestureRecognizer.js) - [SvScreenBottomEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fscreen%2Fsides%2FSvScreenBottomEdgePanGestureRecognizer.js) - [SvScreenLeftEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fscreen%2Fsides%2FSvScreenLeftEdgePanGestureRecognizer.js) - [SvScreenRightEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fscreen%2Fsides%2FSvScreenRightEdgePanGestureRecognizer.js) - [SvScreenTopEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fscreen%2Fsides%2FSvScreenTopEdgePanGestureRecognizer.js) - [SvPinchGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvPinchGestureRecognizer.js) - [SvRotationGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvRotationGestureRecognizer.js) - [SvSlideGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvSlideGestureRecognizer.js) - [SvTapGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvTapGestureRecognizer.js) - [SvHtmlStreamReader](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fhtml%2FHtmlStreamReader%2FSvHtmlStreamReader.js) - [SvHttpResponseCodes](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fnetworking%2FSvHttpResponseCodes.js) - [SvJsonObject](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FSvJsonObject.js) - [SvJsonStreamReader](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fjson%2FJsonStreamReader%2FSvJsonStreamReader.js) - [SvNamespaceSearch](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmisc%2FSvNamespaceSearch.js) - [SvNumberFormatter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fformatters%2FSvNumberFormatter.js) - [SvObjectPool](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2FSvObjectPool.js) - [SvPersistentObjectPool](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2FSvPersistentObjectPool.js) - [SvSubObjectPool](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2FSvSubObjectPool.js) - [SvObservableProxy](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproxies%2FSvObservableProxy.js) - [SvFirewallProxy](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproxies%2FSvFirewallProxy.js) - [SvPersistentAsyncMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fstorage%2FSvPersistentAsyncMap.js) - [SvPoint](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fgeometry%2FSvPoint.js) - [SvEventPoint](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvEventPoint.js) - [ProtoClass_protocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fprotocol%2FProtoClass_protocol.js) - [ProtoClass_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2FProtoClass_store.js) - [ProtoClass_tasks](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2Ftasks%2FProtoClass_tasks.js) - [Protocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fprotocol%2FProtocol.js) - [SvAudioClipDelegateProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Faudio%2FSvAudioClipDelegateProtocol.js) - [SvAudioClipProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Faudio%2FSvAudioClipProtocol.js) - [SvDragDestinationProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvDragDestinationProtocol.js) - [SvDragSourceProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvDragSourceProtocol.js) - [SvRectangle](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fgeometry%2FSvRectangle.js) - [SvSimpleSynth](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Faudio%2FSvSimpleSynth.js) - [SvStackFrame](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmisc%2FSvStackTrace.js) - [SvStackTrace](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmisc%2FSvStackTrace.js) - [SvStreamNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fhtml%2FHtmlStreamReader%2FSvStreamNode.js) - [SvStreamElementNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fhtml%2FHtmlStreamReader%2FSvStreamElementNode.js) - [SvStreamTextNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fhtml%2FHtmlStreamReader%2FSvStreamTextNode.js) - [SvAsyncTimer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2FSvAsyncTimer.js) - [SvBlobPool](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2FSvBlobPool.js) - [SvBroadcaster](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2FSvBroadcaster.js) - [SvDataUrl](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FBrowserDragAndDrop%2FSvDataUrl.js) - [SvErrorCatalog](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Ferrors%2FSvErrorCatalog.js) - [SvErrorCatalog_auth](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Ferrors%2FSvErrorCatalog_auth.js) - [SvErrorCatalog_configuration](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Ferrors%2FSvErrorCatalog_configuration.js) - [SvErrorDefinition](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Ferrors%2FSvErrorDefinition.js) - [SvErrorImageResolver](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Ferrors%2FSvErrorImageResolver.js) - [SvgIconCache](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvgIconCache.js) - [SvI18nCache](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvI18nCache.js) - [SvI18nStore](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvI18nStore.js) - [SvNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvNode.js) - [SvNode_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2FSvNode_store.js) - [SvTitledNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvTitledNode.js) - [SvApp](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2FSvApp.js) - [SvErrorReport](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2FSvErrorReport.js) - [SvTranslatableNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvTranslatableNode.js) - [SvInspectableNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvInspectableNode.js) - [SvViewableNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvViewableNode.js) - [SvStyledNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvStyledNode.js) - [SvBaseNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvBaseNode.js) - [SvActorMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Factor%2FSvActorMessage.js) - [SvActorMessages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Factor%2FSvActorMessages.js) - [SvActorNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Factor%2FSvActorNode.js) - [SvDataStore](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fnodes%2Finspectors%2FSvDataStore.js) - [SvDayNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Fdate%2FSvDayNode.js) - [SvFontFamily](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffonts%2FSvFontFamily.js) - [SvHourNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Ftime%2FSvHourNode.js) - [SvI18n](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvI18n.js) - [SvI18nService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvI18nService.js) - [SvMeridiemNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Ftime%2FSvMeridiemNode.js) - [SvMinuteNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Ftime%2FSvMinuteNode.js) - [SvMonthNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Fdate%2FSvMonthNode.js) - [SvResource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2FSvResource.js) - [SvFont](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffonts%2FSvFont.js) - [SvgIconNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ficons%2FSvgIconNode.js) - [SvImage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fimages%2FSvImage.js) - [SvImage_evaluator](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FSvImage_evaluator.js) - [SvJsonResource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fjson%2FSvJsonResource.js) - [SvURLImage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fimages%2FSvURLImage.js) - [SvWaSound](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fsounds%2FSvWaSound.js) - [SvResourceFile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffiles%2FSvResourceFile.js) - [SvResourceFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffiles%2FSvResourceFolder.js) - [SvResourceGroup](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2FSvResourceGroup.js) - [SvFileResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffiles%2FSvFileResources.js) - [SvFontResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffonts%2FSvFontResources.js) - [SvIconResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ficons%2FSvIconResources.js) - [SvImageResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fimages%2FSvImageResources.js) - [SvJsonResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fjson%2FSvJsonResources.js) - [SvSoundResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fsounds%2FSvSoundResources.js) - [SvWaContext](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fsounds%2FSvWaContext.js) - [SvYearNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Fdate%2FSvYearNode.js) - [SvStorableNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fnodes%2FSvStorableNode.js) - [SvAiRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FRequests%2FSvAiRequest.js) - [SvAnthropicRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAnthropic%2FSvAnthropicRequest.js) - [SvGeminiRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FSvGeminiRequest.js) - [SvOpenAiRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FSvOpenAiRequest.js) - [SvDeepSeekRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FDeepSeek%2FSvDeepSeekRequest.js) - [SvGroqRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGroq%2FSvGroqRequest.js) - [SvXaiRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FXai%2FSvXaiRequest.js) - [SvAzureService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2FSvAzureService.js) - [SvAzureTtsRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fspeakers%2Frequests%2FSvAzureTtsRequest.js) - [SvRzPeer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FSvRzPeer.js) - [SvRzSigServer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FSvRzSigServer.js) - [SvRzSigServerConn](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FSvRzSigServerConn.js) - [SvSpatialModelNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpatial%2FSpatialModelsNode.js) - [SvBlobsNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fblobs%2FSvBlobsNode.js) - [SvCreatorNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2FSvCreatorNode.js) - [SvFieldSetNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2FSvFieldSetNode.js) - [SvDataStoreRecord](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fnodes%2Finspectors%2FSvDataStoreRecord.js) - [SvI18nEntry](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvI18nEntry.js) - [SvModel](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2FSvModel.js) - [SvOptionNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Foptions%2FSvOptionNode.js) - [SvPrototypesNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvPrototypesNode.js) - [SvResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2FSvResources.js) - [SvSummaryNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvSummaryNode.js) - [SvAiChatModel](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiChatModel.js) - [SvAiChatModels](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiChatModels.js) - [SvAiConversations](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiConversations.js) - [SvAiPromptComposer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FComposer%2FSvAiPromptComposer.js) - [SvAiService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiService.js) - [SvAnthropicService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAnthropic%2FSvAnthropicService.js) - [SvDeepSeekService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FDeepSeek%2FSvDeepSeekService.js) - [SvGeminiService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FSvGeminiService.js) - [SvGroqService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGroq%2FSvGroqService.js) - [SvImagineProService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FSvImagineProService.js) - [SvLeonardoService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FSvLeonardoService.js) - [SvOpenAiService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FSvOpenAiService.js) - [SvXaiService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FXai%2FSvXaiService.js) - [SvAssistantToolKit](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FSvAssistantToolKit.js) - [SvAudioQueue](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Faudio%2FSvAudioQueue.js) - [SvAzureLocale](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Flocales%2FSvAzureLocale.js) - [SvAzureLocales](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Flocales%2FSvAzureLocales.js) - [SvAzureSpeaker](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fspeakers%2FSvAzureSpeaker.js) - [SvAzureSpeakers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fspeakers%2FSvAzureSpeakers.js) - [SvAzureTtsRequests](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fspeakers%2Frequests%2FSvAzureTtsRequests.js) - [SvAzureVoice](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fvoices%2FSvAzureVoice.js) - [SvAzureVoices](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fvoices%2FSvAzureVoices.js) - [SvEvalChecklistItem](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FChecklist%2FSvEvalChecklistItem.js) - [SvEvalChecklistItems](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FChecklist%2FSvEvalChecklistItems.js) - [SvFilesToDownload](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2Ffiles%2FSvFilesToDownload.js) - [SvFileToDownload](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2Ffiles%2FSvFileToDownload.js) - [SvFirebaseNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FSvFirebaseNode.js) - [SvFirebaseFile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FFiles%2FSvFirebaseFile.js) - [SvFirebaseFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FFolders%2FSvFirebaseFolder.js) - [SvFirebaseRootFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FFolders%2FSvFirebaseRootFolder.js) - [SvFirebaseService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FSvFirebaseService.js) - [SvFirebaseStorageService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FSvFirebaseStorageService.js) - [SvFirestoreCollections](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FCollections%2FSvFirestoreCollections.js) - [SvFirestoreDatabaseService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FSvFirestoreDatabaseService.js) - [SvFirestoreDocuments](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FDocuments%2FSvFirestoreDocuments.js) - [SvFirestoreNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FSvFirestoreNode.js) - [SvFirestoreCollection](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FCollections%2FSvFirestoreCollection.js) - [SvFirestoreDocument](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FDocuments%2FSvFirestoreDocument.js) - [SvFirestoreQuery](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FSvFirestoreQuery.js) - [SvFirestoreRoot](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FSvFirestoreRoot.js) - [SvGeminiImageUpscalings](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FImage%2520Upscaling%2FSvGeminiImageUpscalings.js) - [SvGeminiVideoPrompt](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FText%2520to%2520Video%2FSvGeminiVideoPrompt.js) - [SvGeminiVideoPrompts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FText%2520to%2520Video%2FSvGeminiVideoPrompts.js) - [SvHomeAssistant](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FAssistants%2FSvHomeAssistant.js) - [SvHomeAssistantFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FSvHomeAssistantFolder.js) - [SvHomeAssistantGroup](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FSvHomeAssistantGroup.js) - [SvHomeAssistantAreas](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FAreas%2FSvHomeAssistantAreas.js) - [SvHomeAssistantDevices](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FDevices%2FSvHomeAssistantDevices.js) - [SvHomeAssistantEntities](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FEntities%2FSvHomeAssistantEntities.js) - [SvHomeAssistantStates](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FStates%2FSvHomeAssistantStates.js) - [SvHomeAssistantObject](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FSvHomeAssistantObject.js) - [SvHomeAssistantArea](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FAreas%2FSvHomeAssistantArea.js) - [SvHomeAssistantDevice](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FDevices%2FSvHomeAssistantDevice.js) - [SvHomeAssistantEntity](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FEntities%2FSvHomeAssistantEntity.js) - [SvHomeAssistantState](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FStates%2FSvHomeAssistantState.js) - [SvHomeAssistants](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FAssistants%2FSvHomeAssistants.js) - [SvImageEvalChecklistMaker](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FSvImageEvalChecklistMaker.js) - [SvImageEvaluator](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FSvImageEvaluator.js) - [SvImageEvaluators](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FSvImageEvaluators.js) - [SvImagineProImageGeneration](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2FSvImagineProImageGeneration.js) - [SvImagineProImageGenerations](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2FSvImagineProImageGenerations.js) - [SvImagineProImagePrompt](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2FSvImagineProImagePrompt.js) - [SvImagineProImageEvalPrompt](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FSvImagineProImageEvalPrompt.js) - [SvLeonardoImage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FText%2520to%2520Image%2Fimages%2FSvLeonardoImage.js) - [SvLeonardoImageGeneration](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FText%2520to%2520Image%2FSvLeonardoImageGeneration.js) - [SvLeonardoImagePrompt](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FText%2520to%2520Image%2FSvLeonardoImagePrompt.js) - [SvLeonardoImagePrompts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FText%2520to%2520Image%2FSvLeonardoImagePrompts.js) - [SvLeonardoImages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FText%2520to%2520Image%2Fimages%2FSvLeonardoImages.js) - [SvLeonardoRefImage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FRefImages%2FSvLeonardoRefImage.js) - [SvLeonardoRefImages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FRefImages%2FSvLeonardoRefImages.js) - [SvLeoStyleTransfer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FStyle%2520Transfers%2FSvLeoStyleTransfer.js) - [SvLeoStyleTransfers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FStyle%2520Transfers%2FSvLeoStyleTransfers.js) - [SvMusicFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FMusicPlayer%2FSvMusicFolder.js) - [SvMusicLibrary](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FMusicPlayer%2FSvMusicLibrary.js) - [SvMusicTrack](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FMusicPlayer%2FSvMusicTrack.js) - [SvOpenAiImage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Image%2Fimages%2FSvOpenAiImage.js) - [SvOpenAiImagePrompt](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Image%2FSvOpenAiImagePrompt.js) - [SvOpenAiImagePrompts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Image%2FSvOpenAiImagePrompts.js) - [SvOpenAiImages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Image%2Fimages%2FSvOpenAiImages.js) - [SvOpenAiStyleTransfer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FStyle%2520Transfers%2FSvOpenAiStyleTransfer.js) - [SvOpenAiStyleTransfers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FStyle%2520Transfers%2FSvOpenAiStyleTransfers.js) - [SvOpenAiTtsRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Speech%2FSvOpenAiTtsRequest.js) - [SvOpenAiTtsSession](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Speech%2FSvOpenAiTtsSession.js) - [SvOpenAiTtsSessions](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Speech%2FSvOpenAiTtsSessions.js) - [SvPeerService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FSvPeerService.js) - [SvProxyServer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FProxyServers%2FSvProxyServer.js) - [SvDefaultProxyServer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FProxyServers%2FSvDefaultProxyServer.js) - [SvProxyServers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FProxyServers%2FSvProxyServers.js) - [SvRzMsg](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FRzPeerConns%2FRzMsgs%2FSvRzMsg.js) - [SvRzMsgs](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FRzPeerConns%2FRzMsgs%2FSvRzMsgs.js) - [SvRzPeerConn](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FRzPeerConns%2FSvRzPeerConn.js) - [SvRzPeerConns](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FRzPeerConns%2FSvRzPeerConns.js) - [SvRzSigServerConns](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FSvRzSigServerConns.js) - [SvRzSigServerPeers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FSvRzSigServerPeers.js) - [SvRzSigServers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FSvRzSigServers.js) - [SvSpeechToTextSession](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpeechToText%2FSvSpeechToTextSession.js) - [SvSpeechToTextSessions](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpeechToText%2FSvSpeechToTextSessions.js) - [SvSttMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpeechToText%2FSttMessages%2FSvSttMessage.js) - [SvSttMessages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpeechToText%2FSttMessages%2FSvSttMessages.js) - [SvAiImageEditor](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FImageEditor%2FSvAiImageEditor.js) - [SvGeminiImageEditor](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FImage%2520Editing%2FSvGeminiImageEditor.js) - [SvGeminiImageScaler](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FImage%2520Upscaling%2FSvGeminiImageScaler.js) - [SvCoachMarkManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvCoachMarkManager.js) - [SvCredential](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fcredentials%2FSvCredential.js) - [SvCredentialManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fcredentials%2FSvCredentialManager.js) - [SvCredentials](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fcredentials%2FSvCredentials.js) - [SvDateNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Fdate%2FSvDateNode.js) - [SvFolderNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvFolderNode.js) - [SvBreadCrumbsNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fapp%2FSvBreadCrumbsNode.js) - [SvGeminiImageEditors](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FImage%2520Editing%2FSvGeminiImageEditors.js) - [SvImageMosaic](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fimage%2FSvImageMosaic.js) - [SvImageMosaics](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fimage%2FSvImageMosaics.js) - [SvJsonArchiver](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonArchiver.js) - [SvJsonIdNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonIdNode.js) - [SvJsonGroup](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonGroup.js) - [SvBlobNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fblobs%2FSvBlobNode.js) - [SvCloudBlobNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fblobs%2FSvCloudBlobNode.js) - [SvImageNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fimage%2FSvImageNode.js) - [SvVideoNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fvideo%2FSvVideoNode.js) - [SvField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2FSvField.js) - [SvActionField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvActionField.js) - [SvBooleanField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvBooleanField.js) - [SvColorField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvColorField.js) - [SvDateField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvDateField.js) - [SvIdentityField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvIdentityField.js) - [SvImageWellField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvImageWellField.js) - [SvImageField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvImageField.js) - [SvJsonField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvJsonField.js) - [SvArrayField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvArrayField.js) - [SvJsonNullField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2FSvJsonNullField.js) - [SvNumberField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvNumberField.js) - [SvOptionsNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Foptions%2FSvOptionsNode.js) - [SvPointerField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvPointerField.js) - [SvStampField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvStampField.js) - [SvStringField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvStringField.js) - [SvPasswordField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvPasswordField.js) - [SvTextAreaField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvTextAreaField.js) - [SvChatInputNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvChatInputNode.js) - [SvConversationMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvConversationMessage.js) - [SvAiMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiMessage.js) - [SvAiResponseMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiResponseMessage.js) - [SvAiParsedResponseMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FParsing%2FSvAiParsedResponseMessage.js) - [SvAiParsedResponseMessage_parsing](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FParsing%2FSvAiParsedResponseMessage_parsing.js) - [SvAiParsedResponseMessage_streaming](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FParsing%2FSvAiParsedResponseMessage_streaming.js) - [SvAiParsedResponseMessage_tagEvents](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FParsing%2F-unused%2FSvAiParsedResponseMessage_tagEvents.js) - [SvAiParsedResponseMessage_voiceNarration](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FParsing%2FSvAiParsedResponseMessage_voiceNarration.js) - [SvUrlField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvUrlField.js) - [SvVideoWellField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvVideoWellField.js) - [SvJsonGroup_patches](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2Fpatches%2FSvJsonGroup_patches.js) - [SvModelReference](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fjson%2FSvModelReference.js) - [SvSyncableJsonGroup](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvSyncableJsonGroup.js) - [SvJsonNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonNode.js) - [SvJsonArrayNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonArrayNode.js) - [SvConversation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvConversation.js) - [SvAiConversation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiConversation.js) - [SvFirebaseFiles](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FFiles%2FSvFirebaseFiles.js) - [SvFirebaseFolders](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FFolders%2FSvFirebaseFolders.js) - [SvImagineProImagePrompts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2FSvImagineProImagePrompts.js) - [SvImagineProImageEvalPrompts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FSvImagineProImageEvalPrompts.js) - [SvImages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fimages%2FSvImages.js) - [SvImagesNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fimage%2FSvImagesNode.js) - [SvJsonArrayNode_patches](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2Fpatches%2FSvJsonArrayNode_patches.js) - [SvSyncableArrayNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fsyncing%2FSvSyncableArrayNode.js) - [SvJsonDictionaryNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonDictionaryNode.js) - [UoJsonDictionaryNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FJson%2FUoJsonDictionaryNode.js) - [SvJsonPatchRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FJson%2FSvJsonPatchRequest.js) - [SvToolCall](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FCalls%2FSvToolCall.js) - [SvToolDefinition](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FDefinitions%2FSvToolDefinition.js) - [SvToolDefinition_anthropic](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAnthropic%2FSvToolDefinition_anthropic.js) - [SvLinkNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvLinkNode.js) - [SvServices](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSvServices.js) - [SvSyncCollectionSource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fsyncing%2FSvSyncCollectionSource.js) - [SvCloudSyncSource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fsyncing%2FSvCloudSyncSource.js) - [SvPublicCloudSource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fsyncing%2FSvPublicCloudSource.js) - [SvLocalResourceSource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fsyncing%2FSvLocalResourceSource.js) - [SvTimeNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Ftime%2FSvTimeNode.js) - [SvWaQueue](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fsounds%2FSvWaQueue.js) - [SvToolCalls](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FCalls%2FSvToolCalls.js) - [SvToolDefinitions](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FDefinitions%2FSvToolDefinitions.js) - [SvToolResult](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FCalls%2FSvToolResult.js) - [SvTextNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvTextNode.js) - [SvThemeFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeFolder.js) - [SvTheme](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvTheme.js) - [SvDefaultTheme](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvDefaultTheme.js) - [SvThemeClass](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeClass.js) - [SvThemeClassChildren](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeClassChildren.js) - [SvThemeState](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeState.js) - [SvThemeStates](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeStates.js) - [SvThemeResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeResources.js) - [SvUserInterface](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2Fui%2FSvUserInterface.js) - [SvCliUserInterface](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2Fui%2FSvCliUserInterface.js) - [SvHeadlessUserInterface](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2Fui%2FSvHeadlessUserInterface.js) - [SvWebUserInterface](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2Fui%2FSvWebUserInterface.js) - [SvXhrRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FRequests%2FSvXhrRequest.js) - [SvYouTubeAudioPlayer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FSvYouTubeAudioPlayer.js) - [SvYouTubeService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FSvYouTubeService.js) - [SvNotification](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2Fnotifications%2FSvNotification.js) - [SvNotificationCenter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2Fnotifications%2FSvNotificationCenter.js) - [SvObservation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2Fnotifications%2FSvObservation.js) - [SvStyleSheet](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvStyleSheet.js) - [SvSyncAction](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2FSvSyncAction.js) - [SvSyncScheduler](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2FSvSyncScheduler.js) - [SvTask](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2Ftasks%2FSvTask.js) - [SvTasks](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2Ftasks%2FSvTasks.js) - [SvTranslationFilter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvTranslationFilter.js) - [SvWebBrowserBattery](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebBrowserBattery.js) - [SvWebBrowserTab](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebBrowserTab.js) - [SvThrashDetector](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvThrashDetector.js) - [SvTimeFormatter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fformatters%2FSvTimeFormatter.js) - [SvTimePeriodFormatter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fformatters%2FSvTimePeriodFormatter.js) - [SvTransform](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fgeometry%2FSvTransform.js) - [SvViewAnimator](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FAttributes%2FSvViewAnimator.js) - [WbCookie](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2Fcookies%2FWbCookie.js) - [WbCookieManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2Fcookies%2FWbCookieManager.js) - [SvWebBrowserCookie](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebBrowserCookie.js) - [SvWebBrowserNotification](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2Fnotifications%2FSvWebBrowserNotification.js) - [SvWebBrowserNotifications](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2Fnotifications%2FSvWebBrowserNotifications.js) - [SvWebBrowserScreen](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebBrowserScreen.js) - [SvWebBrowserWindow](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebBrowserWindow.js) - [SvWebDocument](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebDocument.js) - [SvYouTubePlayerFrame](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FSvYouTubePlayerFrame.js) - [ProtocolAnalyzer](../../resources/class-doc/class_doc.html?path=%2Fdocs%2Fresources%2Fprotocols.js) - [Range](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FRangeShim.js) - [Range_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FRange_ideal.js) - [RegExp_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FRegex_store.js) - [SvResourceIndexer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Findex-builder%2FSvResourceIndexer.js) - [SvResourcesFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Findex-builder%2FSvResourcesFolder.js) - Set - [SvHookedSet](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcollections%2FSvHookedSet.js) - [SvImmutableSet](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FSvImmutableSet.js) - [Set_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FSet_ideal.js) - [Set_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FSet_store.js) - [Slot](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2FSlot.js) - [Slot_promiseWrapper](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2FSlot_promiseWrapper.js) - [SourceInspector](../../resources/class-doc/class_doc.html?path=%2Fdocs%2Fresources%2FSourceInspector%2FSourceInspector.js) - [SvStoreRef](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2FSvStoreRef.js) - String - [String_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FString_ideal.js) - [String_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2Fliterals%2FString_store.js) - [StrvctFile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FStrvctFile.js) - [StrvctFramework](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fmodule%2FStrvctFramework.js) - [SvBase](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvBase.js) - [SvDatabase](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDatabase.js) - [SvDbCache](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbCache.js) - [SvDbColumn](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbColumn.js) - [SvDbDataType](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbDataType.js) - [SvDbRow](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbRow.js) - [SvDbCustomRow](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbCustomRow.js) - [SvDbSchema](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbSchema.js) - [SvDbTable](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbTable.js) - [SvDbCustomTable](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbCustomTable.js) - [SvDbTx](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbTx.js) - [SvHashCache](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvHashCache.js) - [SvIndexedDbFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fserver-only%2FSvIndexedDbFolder.js) - [SvIndexedDbTx](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fserver-only%2FSvIndexedDbTx.js) - [SvBootLoader](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvBootLoader.js) - [SvBootLoadingView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvBootLoadingView.js) - [SvCliBrowser](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fcli%2Fserver-only%2FSvCliBrowser.js) - [SvGlobals](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FSvGlobals.js) - [SvMimeTypeDetector](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fmedia%2Fmime%2FSvMimeTypeDetector.js) - [SvPlatform](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvPlatform.js) - [SvResourceManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvResourceManager.js) - [SvServiceWorker](../../resources/class-doc/class_doc.html?path=%2Fsource%2FServiceWorker%2FSvServiceWorker.js) - [SvUrlResource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvUrlResource.js) - [SvWindowErrorPanel](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvWindowErrorPanel.js) - [Symbol_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FSymbol_store.js) - [TestClass](../../resources/class-doc/class_doc.html?path=%2Fdocs%2Fresources%2FSourceInspector%2Ftest%2FTestClass.js) - [TestRunner](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fserver-only%2Ftests%2FSvIndexedDbTests.js) - [Type](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FType.js) - [URL_promises](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fcategories%2FURL_promises.js) - [w](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fajv%2Fajv7.js) - [XMLHttpRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FXMLHttpRequestShim.js) - [ZJsonValidator](../../resources/class-doc/class_doc.html?path=%2Fexternal-libs%2Fjson%2Fz-schema%2FZJsonValidator.js) --- Source: /docs/Reference/Modules/ # Modules Module hierarchy and file organization. - boot - [Array_promises](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fcategories%2FArray_promises.js) - [Object_categorySupport](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fcategories%2FObject_categorySupport.js) - [Promise_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fcategories%2FPromise_ideal.js) - [StrvctFile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FStrvctFile.js) - [SvBootLoadingView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvBootLoadingView.js) - [SvGlobals](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FSvGlobals.js) - [SvHashCache](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvHashCache.js) - [SvIndexedDbFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fserver-only%2FSvIndexedDbFolder.js) - [SvIndexedDbTx](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fserver-only%2FSvIndexedDbTx.js) - [SvResourceManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvResourceManager.js) - [SvUrlResource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvUrlResource.js) - [URL_promises](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fcategories%2FURL_promises.js) - boot/server-only - [SvIndexedDbFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fserver-only%2FSvIndexedDbFolder.js) - [SvIndexedDbTx](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fserver-only%2FSvIndexedDbTx.js) - browser - stack - Tile - [SvBreadCrumbsTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvBreadCrumbsTile.js) - [SvTile_slideGesture](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile_slideGesture.js) - globals - [SvAiParsedResponseMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FParsing%2FSvAiParsedResponseMessage.js) - [SvAiParsedResponseMessage_parsing](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FParsing%2FSvAiParsedResponseMessage_parsing.js) - [SvAiParsedResponseMessage_streaming](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FParsing%2FSvAiParsedResponseMessage_streaming.js) - [SvAiParsedResponseMessage_tagEvents](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FParsing%2F-unused%2FSvAiParsedResponseMessage_tagEvents.js) - [SvAiParsedResponseMessage_voiceNarration](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FParsing%2FSvAiParsedResponseMessage_voiceNarration.js) - [SvAiPromptComposer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FComposer%2FSvAiPromptComposer.js) - [SvAnthropicRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAnthropic%2FSvAnthropicRequest.js) - [ArrayBuffer_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FArrayBuffer_ideal.js) - [ArrayBuffer_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FArrayBuffer_store.js) - [Array_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FArray_ideal.js) - [Array_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FArray_store.js) - [SvAssistantToolKit](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FSvAssistantToolKit.js) - [SvAtomicMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmap%2FSvAtomicMap.js) - [SvAudioQueue](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Faudio%2FSvAudioQueue.js) - [SvBaseNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvBaseNode.js) - [SvBasicJsonRepairShop](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fjson%2FSvBasicJsonRepairShop.js) - [BigInt_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FBigInt_store.js) - [Boolean_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FBoolean_ideal.js) - [Boolean_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2Fliterals%2FBoolean_store.js) - [SvBreadCrumbsNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fapp%2FSvBreadCrumbsNode.js) - [SvByteFormatter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fformatters%2FSvByteFormatter.js) - [CliWebServer](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FCliWebServer.js) - [ContentBase](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentBase.js) - [ContentCards](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentCards.js) - [ContentImage](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentImage.js) - [ContentKeyValue](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentKeyValue.js) - [ContentOrderedList](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentOrderedList.js) - [ContentTable](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentTable.js) - [ContentText](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentText.js) - [ContentTimeline](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentTimeline.js) - [ContentToc](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentToc.js) - [ContentUnorderedList](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FContentUnorderedList.js) - [Date_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FDate_ideal.js) - [Date_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FDate_store.js) - [SvDocumentation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmisc%2FSvDocumentation.js) - [SvDragSourceProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvDragSourceProtocol.js) - [SvEnumerableWeakMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fweak%2FSvEnumerableWeakMap.js) - [SvEnumerableWeakSet](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fweak%2FSvEnumerableWeakSet.js) - [Error_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FError_ideal.js) - [Error_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FError_store.js) - [SvEvalChecklistItems](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FChecklist%2FSvEvalChecklistItems.js) - [SvFifoMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FSvFifoMap.js) - [FileReader](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FFileReaderShim.js) - [SvFileToDownload](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2Ffiles%2FSvFileToDownload.js) - [SvFilesToDownload](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2Ffiles%2FSvFilesToDownload.js) - [SvFirewallProxy](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproxies%2FSvFirewallProxy.js) - [FontFace](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FFontFaceShim.js) - [SvGeminiVideoPrompt](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FText%2520to%2520Video%2FSvGeminiVideoPrompt.js) - [HTMLCanvasElement](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FImageShim.js) - [HTMLElement_textField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2Fbrowser-only%2FHtmlElement_textField.js) - [SvHeaderTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvHeaderTile.js) - [SvHookedArray](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcollections%2FSvHookedArray.js) - [SvHookedMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcollections%2FSvHookedMap.js) - [SvHookedSet](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcollections%2FSvHookedSet.js) - [SvHtmlStreamReader](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fhtml%2FHtmlStreamReader%2FSvHtmlStreamReader.js) - [SvHttpResponseCodes](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fnetworking%2FSvHttpResponseCodes.js) - [Image](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FImageShim.js) - [SvImageEvaluators](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FSvImageEvaluators.js) - [Image_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FImage_ideal.js) - [Image_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FImage_store.js) - [ImportsIndexer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Findex-builder%2FImportsIndexer.js) - [SvIndexedArray](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcollections%2FSvIndexedArray.js) - [SvInspectableNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvInspectableNode.js) - [SvJsonObject](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FSvJsonObject.js) - [SvJsonPatchRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FJson%2FSvJsonPatchRequest.js) - [SvJsonStreamReader](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fjson%2FJsonStreamReader%2FSvJsonStreamReader.js) - [Map_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FMap_ideal.js) - [Map_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FMap_store.js) - [SvMarkdownRelative](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmarkdown%2FSvMarkdownRelative.js) - [SvMarkdownToc](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmarkdown%2FSvMarkdownToc.js) - [MinimalIndexedDbFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fserver-only%2Ftests%2FMinimalLevelDbWrapper.js) - [SvMissingSlotError](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_copying.js) - [SvMusicLibrary](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FMusicPlayer%2FSvMusicLibrary.js) - [SvNamespaceSearch](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmisc%2FSvNamespaceSearch.js) - [SvNavView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvNavView.js) - [SvNodeView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvNodeView.js) - [SvNumberFormatter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fformatters%2FSvNumberFormatter.js) - [Number_random](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FNumber_random.js) - [Number_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2Fliterals%2FNumber_store.js) - [Object_boot](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fcategories%2FObject_boot.js) - [Object_class](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_class.js) - [Object_copying](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_copying.js) - [Object_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_ideal.js) - [Object_init](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_init.js) - [Object_mutation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_mutation.js) - [Object_notification](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_notification.js) - [Object_puuid](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_puuid.js) - [Object_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FObject_store.js) - [Object_timeouts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fobject%2FObject_timeouts.js) - [SvObservableProxy](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproxies%2FSvObservableProxy.js) - [PageIndex](../../resources/class-doc/class_doc.html?path=%2Fstyle%2Flayout%2FPageIndex.js) - [ProtoClass](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2FProtoClass.js) - [ProtoClass_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2FProtoClass_store.js) - [ProtoClass_tasks](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2Ftasks%2FProtoClass_tasks.js) - [Protocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fprotocol%2FProtocol.js) - [SvProxyServers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FProxyServers%2FSvProxyServers.js) - [Range](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FRangeShim.js) - [Range_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FRange_ideal.js) - [RegExp_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FRegex_store.js) - [SvResourceIndexer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Findex-builder%2FSvResourceIndexer.js) - [SvResourcesFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Findex-builder%2FSvResourcesFolder.js) - [Set_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FSet_ideal.js) - [Set_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FSet_store.js) - [SvSimpleSynth](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Faudio%2FSvSimpleSynth.js) - [Slot_promiseWrapper](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2FSlot_promiseWrapper.js) - [SvSortedArray](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcollections%2FSvSortedArray.js) - [SvStackScrollView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvStackScrollView.js) - [SvStackView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvStackView.js) - [SvStoreRef](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2FSvStoreRef.js) - [SvStreamElementNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fhtml%2FHtmlStreamReader%2FSvStreamElementNode.js) - [SvStreamNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fhtml%2FHtmlStreamReader%2FSvStreamNode.js) - [SvStreamTextNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fhtml%2FHtmlStreamReader%2FSvStreamTextNode.js) - [String_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FString_ideal.js) - [String_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2Fliterals%2FString_store.js) - [StrvctFramework](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fmodule%2FStrvctFramework.js) - [SvStyledNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvStyledNode.js) - [SvSubviewsDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvSubviewsDomView.js) - [SvActionField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvActionField.js) - [SvActionFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvActionFieldTile.js) - [SvActorMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Factor%2FSvActorMessage.js) - [SvActorMessages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Factor%2FSvActorMessages.js) - [SvActorNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Factor%2FSvActorNode.js) - [SvArrayField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvArrayField.js) - [SvBase](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvBase.js) - [SvBlobNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fblobs%2FSvBlobNode.js) - [SvBlobsNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fblobs%2FSvBlobsNode.js) - [SvBooleanField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvBooleanField.js) - [SvBooleanFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvBooleanFieldTile.js) - [SvBootLoader](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvBootLoader.js) - [SvChatInputTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvChatInputTile.js) - [SvChatMessageTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvChatMessageTile.js) - [SvCliBrowser](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fcli%2Fserver-only%2FSvCliBrowser.js) - [SvCliUserInterface](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2Fui%2FSvCliUserInterface.js) - [SvCloudBlobNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fblobs%2FSvCloudBlobNode.js) - [SvColorField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvColorField.js) - [SvCreatorNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2FSvCreatorNode.js) - [SvCredential](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fcredentials%2FSvCredential.js) - [SvCredentialManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fcredentials%2FSvCredentialManager.js) - [SvCredentials](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fcredentials%2FSvCredentials.js) - [SvDataStore](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fnodes%2Finspectors%2FSvDataStore.js) - [SvDataStoreRecord](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fnodes%2Finspectors%2FSvDataStoreRecord.js) - [SvDateNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Fdate%2FSvDateNode.js) - [SvDayNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Fdate%2FSvDayNode.js) - [SvErrorCatalog](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Ferrors%2FSvErrorCatalog.js) - [SvErrorCatalog_auth](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Ferrors%2FSvErrorCatalog_auth.js) - [SvErrorCatalog_configuration](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Ferrors%2FSvErrorCatalog_configuration.js) - [SvErrorDefinition](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Ferrors%2FSvErrorDefinition.js) - [SvErrorImageResolver](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Ferrors%2FSvErrorImageResolver.js) - [SvErrorReport](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2FSvErrorReport.js) - [SvFieldSetNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2FSvFieldSetNode.js) - [SvFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvFieldTile.js) - [SvHeadlessUserInterface](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2Fui%2FSvHeadlessUserInterface.js) - [SvHourNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Ftime%2FSvHourNode.js) - [SvIdentityField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvIdentityField.js) - [SvImageField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvImageField.js) - [SvImageView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvImageView.js) - [SvImageWellField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvImageWellField.js) - [SvImageWellFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvImageWellFieldTile.js) - [SvImage_evaluator](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FSvImage_evaluator.js) - [SvImages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fimages%2FSvImages.js) - [SvImagesNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fimage%2FSvImagesNode.js) - [SvJsonArchiver](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonArchiver.js) - [SvJsonArrayNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonArrayNode.js) - [SvJsonArrayNode_patches](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2Fpatches%2FSvJsonArrayNode_patches.js) - [SvJsonField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvJsonField.js) - [SvJsonGroup](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonGroup.js) - [SvJsonNullField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2FSvJsonNullField.js) - [SvJsonPatchError](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2Fpatches%2FSvJsonPatchError.js) - [SvLinkNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvLinkNode.js) - [SvMeridiemNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Ftime%2FSvMeridiemNode.js) - [SvMinuteNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Ftime%2FSvMinuteNode.js) - [SvModel](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2FSvModel.js) - [SvModelReference](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fjson%2FSvModelReference.js) - [SvNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvNode.js) - [SvNode_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2FSvNode_store.js) - [SvOptionNodeTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2Foptions%2FSvOptionNodeTile.js) - [SvOptionsNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Foptions%2FSvOptionsNode.js) - [SvOptionsNodeTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2Foptions%2FSvOptionsNodeTile.js) - [SvPasswordFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvPasswordFieldTile.js) - [SvPasswordView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvPasswordView.js) - [SvPlatform](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvPlatform.js) - [SvPointerField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvPointerField.js) - [SvPointerFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvPointerFieldTile.js) - [SvPrototypesNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvPrototypesNode.js) - [SvServiceWorker](../../resources/class-doc/class_doc.html?path=%2Fsource%2FServiceWorker%2FSvServiceWorker.js) - [SvStampField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvStampField.js) - [SvStorableNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fnodes%2FSvStorableNode.js) - [SvStringFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvStringFieldTile.js) - [SvSummaryNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvSummaryNode.js) - [SvTask](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2Ftasks%2FSvTask.js) - [SvTasks](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2Ftasks%2FSvTasks.js) - [SvTextAreaField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvTextAreaField.js) - [SvTextAreaFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvTextAreaFieldTile.js) - [SvTextNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvTextNode.js) - [SvTextNodeTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTextNodeTile.js) - [SvTextView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvTextView.js) - [SvTimeNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Ftime%2FSvTimeNode.js) - [SvUrlField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvUrlField.js) - [SvUserInterface](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2Fui%2FSvUserInterface.js) - [SvVideoNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fvideo%2FSvVideoNode.js) - [SvVideoView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvVideoView.js) - [SvVideoWellField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvVideoWellField.js) - [SvVideoWellFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Ffield_tiles%2FSvVideoWellFieldTile.js) - [SvVideoWellView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvVideoWellView.js) - [SvWaSound](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fsounds%2FSvWaSound.js) - [SvWebBrowserTab](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebBrowserTab.js) - [SvWebUserInterface](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2Fui%2FSvWebUserInterface.js) - [Symbol_store](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2Fcategories%2Fprimitives%2FSymbol_store.js) - [TestRunner](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2Fserver-only%2Ftests%2FSvIndexedDbTests.js) - [SvTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile.js) - [SvTileContainer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvTileContainer.js) - [SvTileNoteButtonView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Fsubviews%2FSvTileNoteButtonView.js) - [SvTileNoteView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Fsubviews%2FSvTileNoteView.js) - [SvTileSubtitleView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Fsubviews%2FSvTileSubtitleView.js) - [SvTileTitleView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2Fsubviews%2FSvTileTitleView.js) - [SvTile_dragging](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile_dragging.js) - [SvTile_keyboard](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile_keyboard.js) - [SvTile_styling](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile_styling.js) - [SvTilesView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView.js) - [SvTilesView_dragViewProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_dragViewProtocol.js) - [SvTilesView_gestures](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_gestures.js) - [SvTilesView_helpers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_helpers.js) - [SvTilesView_keyboard](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_keyboard.js) - [SvTilesView_orientation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_orientation.js) - [SvTilesView_selection](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_selection.js) - [SvTilesView_styling](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvTilesView_styling.js) - [SvTimeFormatter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fformatters%2FSvTimeFormatter.js) - [SvTimePeriodFormatter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fformatters%2FSvTimePeriodFormatter.js) - [SvTitledNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvTitledNode.js) - [SvTitledTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTitledTile.js) - [SvToolCall](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FCalls%2FSvToolCall.js) - [SvToolCalls](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FCalls%2FSvToolCalls.js) - [SvToolDefinition](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FDefinitions%2FSvToolDefinition.js) - [SvToolDefinition_anthropic](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAnthropic%2FSvToolDefinition_anthropic.js) - [SvToolDefinitions](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FDefinitions%2FSvToolDefinitions.js) - [SvToolResult](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FCalls%2FSvToolResult.js) - [UoJsonDictionaryNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FTools%2FJson%2FUoJsonDictionaryNode.js) - [XMLHttpRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2Fserver-only%2FXMLHttpRequestShim.js) - library - i18n - [SvI18n](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvI18n.js) - [SvI18nCache](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvI18nCache.js) - [SvI18nEntry](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvI18nEntry.js) - [SvI18nService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvI18nService.js) - [SvI18nStore](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvI18nStore.js) - [SvTranslationFilter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fi18n%2FSvTranslationFilter.js) - ideal - [SvImmutableSet](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FSvImmutableSet.js) - [Number_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FNumber_ideal.js) - [Type](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FType.js) - categories - [Blob_ideal](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FBlob_ideal.js) - [Mirror](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fcategories%2FMirror.js) - misc - [SvStackFrame](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmisc%2FSvStackTrace.js) - [SvStackTrace](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fmisc%2FSvStackTrace.js) - proto - [Slot](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fproto%2FSlot.js) - protocol - [ProtoClass_protocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fideal%2Fprotocol%2FProtoClass_protocol.js) - image - [SvImageMosaic](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fimage%2FSvImageMosaic.js) - [SvImageMosaics](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fimage%2FSvImageMosaics.js) - media - mime - [SvMimeTypeDetector](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fmedia%2Fmime%2FSvMimeTypeDetector.js) - node - [SvApp](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2FSvApp.js) - [SvAsyncTimer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fapp%2FSvAsyncTimer.js) - audio - [SvAudioClipDelegateProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Faudio%2FSvAudioClipDelegateProtocol.js) - [SvAudioClipProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Faudio%2FSvAudioClipProtocol.js) - fields - [SvField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2FSvField.js) - json - [SvJsonDictionaryNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonDictionaryNode.js) - [SvJsonIdNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonIdNode.js) - [SvJsonNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvJsonNode.js) - patches - [SvJsonGroup_patches](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2Fpatches%2FSvJsonGroup_patches.js) - subclasses - [SvDateField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvDateField.js) - [SvNumberField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvNumberField.js) - [SvPasswordField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvPasswordField.js) - [SvStringField](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2FSvStringField.js) - date - [SvMonthNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Fdate%2FSvMonthNode.js) - [SvYearNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Fdate%2FSvYearNode.js) - options - [SvOptionNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fsubclasses%2Foptions%2FSvOptionNode.js) - json - [SvSyncableJsonGroup](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fjson%2FSvSyncableJsonGroup.js) - node_views - [SvCoachableView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvCoachableView.js) - [SvImageWellView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2FSvImageWellView.js) - browser - [SvBrowserView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2FSvBrowserView.js) - stack - [SvScrollContentView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvScrollContentView.js) - [SvScrollView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvScrollView.js) - [SvScrollToBottomButton](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FSvScrollToBottomButton.js) - Tile - [SvTile_gestures](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTile%2FSvTile_gestures.js) - TilesView - [SvDragDestinationProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvDragDestinationProtocol.js) - nodes - [SvFolderNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvFolderNode.js) - [SvImageNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Ffields%2Fimage%2FSvImageNode.js) - base - [SvTranslatableNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvTranslatableNode.js) - [SvViewableNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fbase%2FSvViewableNode.js) - syncing - [SvCloudSyncSource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fsyncing%2FSvCloudSyncSource.js) - [SvLocalResourceSource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fsyncing%2FSvLocalResourceSource.js) - [SvPublicCloudSource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fsyncing%2FSvPublicCloudSource.js) - [SvSyncCollectionSource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fsyncing%2FSvSyncCollectionSource.js) - [SvSyncableArrayNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2Fsyncing%2FSvSyncableArrayNode.js) - storage - base - [SvObjectPool](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2FSvObjectPool.js) - [SvPersistentObjectPool](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2FSvPersistentObjectPool.js) - [SvSubObjectPool](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2FSvSubObjectPool.js) - [SvBlobPool](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fstorage%2Fbase%2FSvBlobPool.js) - SubnodesArray - [SvSubnodesArray](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2FSvSubnodesArray.js) - notification - [SvBroadcaster](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2FSvBroadcaster.js) - [SvSyncAction](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2FSvSyncAction.js) - [SvSyncScheduler](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2FSvSyncScheduler.js) - notifications - [SvNotification](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2Fnotifications%2FSvNotification.js) - [SvNotificationCenter](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2Fnotifications%2FSvNotificationCenter.js) - [SvObservation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnotification%2Fnotifications%2FSvObservation.js) - resources - [SvMimeExtensions](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FSvMimeExtensions.js) - [SvResource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2FSvResource.js) - [SvResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2FSvResources.js) - files - [SvFileResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffiles%2FSvFileResources.js) - [SvResourceFile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffiles%2FSvResourceFile.js) - [SvResourceFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffiles%2FSvResourceFolder.js) - fonts - [SvFont](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffonts%2FSvFont.js) - [SvFontFamily](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffonts%2FSvFontFamily.js) - [SvFontResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffonts%2FSvFontResources.js) - [SvFontTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ffonts%2FSvFontTile.js) - icons - [SvIconResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ficons%2FSvIconResources.js) - [SvResourceGroup](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2FSvResourceGroup.js) - [SvgIconNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Ficons%2FSvgIconNode.js) - images - [SvImage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fimages%2FSvImage.js) - [SvImageResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fimages%2FSvImageResources.js) - [SvImageTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fimages%2FSvImageTile.js) - [SvURLImage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fimages%2FSvURLImage.js) - json - [SvJsonResource](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fjson%2FSvJsonResource.js) - [SvJsonResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fjson%2FSvJsonResources.js) - sounds - [SvSoundResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fsounds%2FSvSoundResources.js) - [SvWaContext](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fsounds%2FSvWaContext.js) - [SvWaQueue](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fsounds%2FSvWaQueue.js) - themes - [SvDefaultTheme](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvDefaultTheme.js) - [SvTheme](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvTheme.js) - [SvThemeClass](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeClass.js) - [SvThemeClassChildren](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeClassChildren.js) - [SvThemeFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeFolder.js) - [SvThemeResources](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeResources.js) - [SvThemeState](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeState.js) - [SvThemeStates](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fresources%2Fthemes%2FSvThemeStates.js) - services - [SvServices](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSvServices.js) - AiServiceKit - [SvAiChatModel](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiChatModel.js) - [SvAiChatModels](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiChatModels.js) - [SvAiConversation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiConversation.js) - [SvAiConversations](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiConversations.js) - [SvAiMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiMessage.js) - [SvAiRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FRequests%2FSvAiRequest.js) - [SvAiResponseMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiResponseMessage.js) - [SvAiService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvAiService.js) - [SvChatInputNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvChatInputNode.js) - [SvConversation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvConversation.js) - [SvConversationMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FSvConversationMessage.js) - [SvXhrRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FRequests%2FSvXhrRequest.js) - ImageEditor - [SvAiImageEditor](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FImageEditor%2FSvAiImageEditor.js) - Requests - errors - [SvAiRequestOverloadedError](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAiServiceKit%2FRequests%2Ferrors%2FSvAiRequestOverloadedError.js) - Anthropic - [SvAnthropicService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAnthropic%2FSvAnthropicService.js) - Azure - [SvAzureService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2FSvAzureService.js) - locales - [SvAzureLocale](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Flocales%2FSvAzureLocale.js) - [SvAzureLocales](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Flocales%2FSvAzureLocales.js) - speakers - [SvAzureSpeaker](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fspeakers%2FSvAzureSpeaker.js) - [SvAzureSpeakers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fspeakers%2FSvAzureSpeakers.js) - requests - [SvAzureTtsRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fspeakers%2Frequests%2FSvAzureTtsRequest.js) - [SvAzureTtsRequests](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fspeakers%2Frequests%2FSvAzureTtsRequests.js) - voices - [SvAzureVoice](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fvoices%2FSvAzureVoice.js) - [SvAzureVoices](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FAzure%2Fvoices%2FSvAzureVoices.js) - DeepSeek - [SvDeepSeekRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FDeepSeek%2FSvDeepSeekRequest.js) - [SvDeepSeekService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FDeepSeek%2FSvDeepSeekService.js) - Firebase - [SvFirebaseFile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FFiles%2FSvFirebaseFile.js) - [SvFirebaseFiles](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FFiles%2FSvFirebaseFiles.js) - [SvFirebaseFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FFolders%2FSvFirebaseFolder.js) - [SvFirebaseFolders](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FFolders%2FSvFirebaseFolders.js) - [SvFirebaseNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FSvFirebaseNode.js) - [SvFirebaseRootFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FFolders%2FSvFirebaseRootFolder.js) - [SvFirebaseService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FSvFirebaseService.js) - [SvFirebaseStoragePermissions](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FSvFirebaseStoragePermissions.js) - [SvFirebaseStorageService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirebaseStorage%2FSvFirebaseStorageService.js) - [SvFirestoreCollection](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FCollections%2FSvFirestoreCollection.js) - [SvFirestoreCollections](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FCollections%2FSvFirestoreCollections.js) - [SvFirestoreDatabaseService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FSvFirestoreDatabaseService.js) - [SvFirestoreDocument](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FDocuments%2FSvFirestoreDocument.js) - [SvFirestoreDocuments](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FDocuments%2FSvFirestoreDocuments.js) - [SvFirestoreNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FSvFirestoreNode.js) - [SvFirestoreQuery](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FSvFirestoreQuery.js) - [SvFirestoreRoot](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FFirebase%2FFirestoreDatabase%2FSvFirestoreRoot.js) - Gemini - [SvGeminiRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FSvGeminiRequest.js) - [SvGeminiService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FSvGeminiService.js) - Image_Editing - [SvGeminiImageEditor](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FImage%2520Editing%2FSvGeminiImageEditor.js) - [SvGeminiImageEditors](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FImage%2520Editing%2FSvGeminiImageEditors.js) - Image_Upscaling - [SvGeminiImageUpscalings](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FImage%2520Upscaling%2FSvGeminiImageUpscalings.js) - [SvGeminiImageScaler](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FImage%2520Upscaling%2FSvGeminiImageScaler.js) - Text_to_Video - [SvGeminiVideoPrompts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGemini%2FText%2520to%2520Video%2FSvGeminiVideoPrompts.js) - Groq - [SvGroqRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGroq%2FSvGroqRequest.js) - [SvGroqService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FGroq%2FSvGroqService.js) - HomeAssistant - [SvHomeAssistantFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FSvHomeAssistantFolder.js) - [SvHomeAssistantGroup](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FSvHomeAssistantGroup.js) - [SvHomeAssistantObject](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FSvHomeAssistantObject.js) - Areas - [SvHomeAssistantArea](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FAreas%2FSvHomeAssistantArea.js) - [SvHomeAssistantAreas](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FAreas%2FSvHomeAssistantAreas.js) - Assistants - [SvHomeAssistant](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FAssistants%2FSvHomeAssistant.js) - [SvHomeAssistants](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FAssistants%2FSvHomeAssistants.js) - Devices - [SvHomeAssistantDevice](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FDevices%2FSvHomeAssistantDevice.js) - [SvHomeAssistantDevices](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FDevices%2FSvHomeAssistantDevices.js) - Entities - [SvHomeAssistantEntities](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FEntities%2FSvHomeAssistantEntities.js) - [SvHomeAssistantEntity](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FEntities%2FSvHomeAssistantEntity.js) - States - [SvHomeAssistantState](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FStates%2FSvHomeAssistantState.js) - [SvHomeAssistantStates](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FHomeAssistant%2FStates%2FSvHomeAssistantStates.js) - ImaginePro - [SvEvalChecklistItem](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FChecklist%2FSvEvalChecklistItem.js) - [SvImageEvalChecklistMaker](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FSvImageEvalChecklistMaker.js) - [SvImageEvaluator](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FImageEvaluator%2FSvImageEvaluator.js) - [SvImagineProService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FSvImagineProService.js) - Image_Eval_Prompts - [SvImagineProImageEvalPrompt](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FSvImagineProImageEvalPrompt.js) - [SvImagineProImageEvalPrompts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FImage%2520Eval%2520Prompts%2FSvImagineProImageEvalPrompts.js) - Text_to_Image - [SvImagineProImageGeneration](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2FSvImagineProImageGeneration.js) - [SvImagineProImageGenerations](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2FSvImagineProImageGenerations.js) - [SvImagineProImagePrompt](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2FSvImagineProImagePrompt.js) - [SvImagineProImagePrompts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FImaginePro%2FText%2520to%2520Image%2FSvImagineProImagePrompts.js) - Leonardo - [SvLeonardoService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FSvLeonardoService.js) - RefImages - [SvLeoStyleTransfers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FStyle%2520Transfers%2FSvLeoStyleTransfers.js) - [SvLeonardoRefImage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FRefImages%2FSvLeonardoRefImage.js) - [SvLeonardoRefImages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FRefImages%2FSvLeonardoRefImages.js) - StyleTransfers - [SvLeoStyleTransfer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FStyle%2520Transfers%2FSvLeoStyleTransfer.js) - Text_to_Image - [SvLeonardoImageGeneration](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FText%2520to%2520Image%2FSvLeonardoImageGeneration.js) - [SvLeonardoImagePrompt](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FText%2520to%2520Image%2FSvLeonardoImagePrompt.js) - [SvLeonardoImagePrompts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FText%2520to%2520Image%2FSvLeonardoImagePrompts.js) - images - [SvLeonardoImage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FText%2520to%2520Image%2Fimages%2FSvLeonardoImage.js) - [SvLeonardoImages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FLeonardo%2FText%2520to%2520Image%2Fimages%2FSvLeonardoImages.js) - OpenAI - [SvOpenAiRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FSvOpenAiRequest.js) - [SvOpenAiService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FSvOpenAiService.js) - StyleTransfers - [SvOpenAiStyleTransfer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FStyle%2520Transfers%2FSvOpenAiStyleTransfer.js) - [SvOpenAiStyleTransfers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FStyle%2520Transfers%2FSvOpenAiStyleTransfers.js) - Text_to_Image - [SvOpenAiImagePrompt](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Image%2FSvOpenAiImagePrompt.js) - [SvOpenAiImagePrompts](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Image%2FSvOpenAiImagePrompts.js) - images - [SvOpenAiImage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Image%2Fimages%2FSvOpenAiImage.js) - [SvOpenAiImages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Image%2Fimages%2FSvOpenAiImages.js) - Text_to_Speech - [SvOpenAiTtsRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Speech%2FSvOpenAiTtsRequest.js) - [SvOpenAiTtsSession](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Speech%2FSvOpenAiTtsSession.js) - [SvOpenAiTtsSessions](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FOpenAI%2FText%2520to%2520Speech%2FSvOpenAiTtsSessions.js) - Peer - [SvPeerService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FSvPeerService.js) - RzSigServers - [SvRzPeer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FSvRzPeer.js) - [SvRzSigServer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FSvRzSigServer.js) - [SvRzSigServerPeers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FSvRzSigServerPeers.js) - [SvRzSigServers](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FSvRzSigServers.js) - RzSigServerConns - [SvRzSigServerConn](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FSvRzSigServerConn.js) - [SvRzSigServerConns](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FSvRzSigServerConns.js) - RzPeerConns - [SvRzPeerConn](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FRzPeerConns%2FSvRzPeerConn.js) - [SvRzPeerConns](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FRzPeerConns%2FSvRzPeerConns.js) - RzMsgs - [SvRzMsg](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FRzPeerConns%2FRzMsgs%2FSvRzMsg.js) - [SvRzMsgs](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FPeer%2FRzSigServers%2FRzSigServerConns%2FRzPeerConns%2FRzMsgs%2FSvRzMsgs.js) - ProxyServers - [SvDefaultProxyServer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FProxyServers%2FSvDefaultProxyServer.js) - [SvProxyServer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FProxyServers%2FSvProxyServer.js) - Spatial - [SvSceneView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpatial%2FSvSceneView.js) - [SvSceneViewWellFieldTile](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpatial%2FSvSceneViewWellFieldTile.js) - [SvSpatialModelNode](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpatial%2FSpatialModelsNode.js) - SpeechToText - [SvSpeechToTextSession](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpeechToText%2FSvSpeechToTextSession.js) - [SvSpeechToTextSessions](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpeechToText%2FSvSpeechToTextSessions.js) - SttMessages - [SvSttMessage](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpeechToText%2FSttMessages%2FSvSttMessage.js) - [SvSttMessages](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FSpeechToText%2FSttMessages%2FSvSttMessages.js) - Xai - [SvXaiRequest](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FXai%2FSvXaiRequest.js) - [SvXaiService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FXai%2FSvXaiService.js) - YouTube - [SvYouTubeAudioPlayer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FSvYouTubeAudioPlayer.js) - [SvYouTubePlayerFrame](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FSvYouTubePlayerFrame.js) - [SvYouTubeService](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FSvYouTubeService.js) - MusicPlayer - [SvMusicFolder](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FMusicPlayer%2FSvMusicFolder.js) - [SvMusicTrack](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fservices%2FYouTube%2FMusicPlayer%2FSvMusicTrack.js) - storage - [SvPersistentAsyncMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fstorage%2FSvPersistentAsyncMap.js) - [SvPersistentAtomicMap](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fstorage%2FSvPersistentAtomicMap.js) - view - dom - Attributes - [SvCssAnimation](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FAttributes%2FSvCssAnimation.js) - [SvDomBorderRadius](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FAttributes%2FSvDomBorderRadius.js) - [SvDomTransition](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FAttributes%2FSvDomTransition.js) - [SvDomTransitions](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FAttributes%2FSvDomTransitions.js) - [SvViewAnimator](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FAttributes%2FSvViewAnimator.js) - BrowserDragAndDrop - [SvDataUrl](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FBrowserDragAndDrop%2FSvDataUrl.js) - DomView - [SvControlDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvControlDomView.js) - [SvCssDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvCssDomView.js) - [SvDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvDomView.js) - [SvDomView_animations](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvDomView_animations.js) - [SvDomView_browserDragAndDrop](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvDomView_browserDragAndDrop.js) - [SvEditableDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvEditableDomView.js) - [SvElementDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvElementDomView.js) - [SvFlexDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvFlexDomView.js) - [SvGesturableDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvGesturableDomView.js) - [SvListenerDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvListenerDomView.js) - [SvResponderDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvResponderDomView.js) - [SvSelectableDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvSelectableDomView.js) - [SvStyledDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvStyledDomView.js) - [SvVisibleDomView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2FSvVisibleDomView.js) - subclasses - [SvBooleanView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvBooleanView.js) - [SvButtonView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvButtonView.js) - [SvCloseButton](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvCloseButton.js) - [SvDocumentBody](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvDocumentBody.js) - [SvDragBarView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvDragBarView.js) - [SvDragView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvDragView.js) - [SvCoachMarkView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2Fcoachmarks%2FSvCoachMarkView.js) - [SvOverlayBannerView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvOverlayBannerView.js) - [SvPanelView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvPanelView.js) - [SvScrimView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvScrimView.js) - [SvWindowErrorPanel](../../resources/class-doc/class_doc.html?path=%2Fsource%2Fboot%2FSvWindowErrorPanel.js) - [SvgIconCache](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvgIconCache.js) - [SvgIconView](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FDomView%2Fsubclasses%2FSvgIconView.js) - coachmarks - [SvCoachMarkManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnodes%2FSvCoachMarkManager.js) - Helpers - [SvCssColor](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FHelpers%2FSvCssColor.js) - [SvDomCssInspector](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FHelpers%2FSvDomCssInspector.js) - TapeMeasures - [SvCanvasTextTapeMeasure](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FHelpers%2FTapeMeasures%2FSvCanvasTextTapeMeasure.js) - [SvDomTextTapeMeasure](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fdom%2FHelpers%2FTapeMeasures%2FSvDomTextTapeMeasure.js) - events - devices - [SvDevice](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvDevice.js) - [SvDevices](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvDevices.js) - [SvEventPoint](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvEventPoint.js) - [SvGamePad](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvGamePad.js) - [SvGamePadManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvGamePadManager.js) - [SvKeyboardKey](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvKeyboardKey.js) - [SvMouse](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvMouse.js) - [SvKeyboard](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvKeyboard.js) - [SvTouchScreen](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fdevices%2FSvTouchScreen.js) - gesturing - [SvGestureManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2FSvGestureManager.js) - [SvGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2FSvGestureRecognizer.js) - gestures - [SvLongPressGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvLongPressGestureRecognizer.js) - [SvOrientGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvOrientGestureRecognizer.js) - [SvPanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvPanGestureRecognizer.js) - [SvPinchGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvPinchGestureRecognizer.js) - [SvRotationGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvRotationGestureRecognizer.js) - [SvSlideGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvSlideGestureRecognizer.js) - [SvTapGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2FSvTapGestureRecognizer.js) - edges - [SvEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2FSvEdgePanGestureRecognizer.js) - screen - [SvScreenEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fscreen%2FSvScreenEdgePanGestureRecognizer.js) - sides - [SvScreenBottomEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fscreen%2Fsides%2FSvScreenBottomEdgePanGestureRecognizer.js) - [SvScreenLeftEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fscreen%2Fsides%2FSvScreenLeftEdgePanGestureRecognizer.js) - [SvScreenRightEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fscreen%2Fsides%2FSvScreenRightEdgePanGestureRecognizer.js) - [SvScreenTopEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fscreen%2Fsides%2FSvScreenTopEdgePanGestureRecognizer.js) - view - [SvBottomEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fview%2FSvBottomEdgePanGestureRecognizer.js) - [SvLeftEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fview%2FSvLeftEdgePanGestureRecognizer.js) - [SvRightEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fview%2FSvRightEdgePanGestureRecognizer.js) - [SvTopEdgePanGestureRecognizer](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Fgesturing%2Fgestures%2Fedges%2Fview%2FSvTopEdgePanGestureRecognizer.js) - listening - [SvEventListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2FSvEventListener.js) - [SvEventManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2FSvEventManager.js) - [SvEventSetListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2FSvEventSetListener.js) - listeners - [SvAnimationListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvAnimationListener.js) - [SvBatteryListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvBatteryListener.js) - [SvClipboardListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvClipboardListener.js) - [SvDocumentListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvDocumentListener.js) - [SvDragListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvDragListener.js) - [SvDropListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvDropListener.js) - [SvFocusListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvFocusListener.js) - [SvGamePadListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvGamePadListener.js) - [SvKeyboardListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvKeyboardListener.js) - [SvMouseListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvMouseListener.js) - [SvMouseMoveListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvMouseMoveListener.js) - [SvScrollListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvScrollListener.js) - [SvSelectListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvSelectListener.js) - [SvSpeechListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvSpeechListener.js) - [SvTouchListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvTouchListener.js) - [SvTouchMoveListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvTouchMoveListener.js) - [SvTransitionListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvTransitionListener.js) - [SvWebSocketListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvWebSocketListener.js) - [SvWheelListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvWheelListener.js) - [SvWindowListener](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fevents%2Flistening%2Flisteners%2FSvWindowListener.js) - geometry - [SvPoint](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fgeometry%2FSvPoint.js) - [SvRectangle](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fgeometry%2FSvRectangle.js) - [SvTransform](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fgeometry%2FSvTransform.js) - webbrowser - [SvStyleSheet](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvStyleSheet.js) - [SvWebBrowserBattery](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebBrowserBattery.js) - [SvThrashDetector](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvThrashDetector.js) - [WbCookie](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2Fcookies%2FWbCookie.js) - [WbCookieManager](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2Fcookies%2FWbCookieManager.js) - [SvWebBrowserCookie](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebBrowserCookie.js) - [SvWebBrowserNotification](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2Fnotifications%2FSvWebBrowserNotification.js) - [SvWebBrowserNotifications](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2Fnotifications%2FSvWebBrowserNotifications.js) - [SvWebBrowserScreen](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebBrowserScreen.js) - [SvWebBrowserWindow](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebBrowserWindow.js) - [SvWebDocument](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fview%2Fwebbrowser%2FSvWebDocument.js) - local-web-server - [Base](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FBase.js) - [SvMimeExtensions](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FSvMimeExtensions.js) - WebServer - [AcmeChallengeRequest](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2Frequests%2FAcmeChallengeRequest.js) - [BaseHttpsServer](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FBaseHttpsServer.js) - [BaseHttpsServerRequest](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2FBaseHttpsServerRequest.js) - [FileRequest](../../resources/class-doc/class_doc.html?path=%2Fwebserver%2Frequests%2FFileRequest.js) - webserver/orm - [SvDatabase](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDatabase.js) - [SvDbCache](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbCache.js) - [SvDbColumn](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbColumn.js) - [SvDbCustomRow](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbCustomRow.js) - [SvDbCustomTable](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbCustomTable.js) - [SvDbDataType](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbDataType.js) - [SvDbRow](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbRow.js) - [SvDbSchema](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbSchema.js) - [SvDbTable](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbTable.js) - [SvDbTx](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Form%2FSvDbTx.js) --- Source: /docs/Reference/Protocols/ # Protocols Protocol definitions and implementations. - library - node - audio - [SvAudioClipDelegateProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Faudio%2FSvAudioClipDelegateProtocol.js) - [SvAudioClipProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Faudio%2FSvAudioClipProtocol.js) - node_views - browser - stack - TilesView - [SvDragDestinationProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvDragDestinationProtocol.js) - [SvDragSourceProtocol](../../resources/class-doc/class_doc.html?path=%2Fsource%2Flibrary%2Fnode%2Fnode_views%2Fbrowser%2Fstack%2FTilesView%2FSvDragSourceProtocol.js) --- Source: /docs/Services/ # Services AI, cloud, media, and IoT service integrations. ## Overview Services are not part of the Strvct core — they are optional integrations provided as a convenient way to quickly connect with important external services. The `Services` singleton is the root container for all of them. Each service is a standard Strvct node with slots, persistence, and automatic UI — the same patterns used for domain objects apply to service configuration. Users can inspect and configure services (API keys, model selection, proxy settings) through the generated interface without any custom settings UI. - AI Services - Cloud Storage - Media - Proxies - Home Assistant --- Source: /docs/Services/AI%20Services/ # AI Services Base classes, requests, conversations, tool calling, response parsing, and prompt composition. ## SvAiService The base class for AI service providers. Each subclass is a singleton representing one provider (Anthropic, OpenAI, Gemini, etc.). Key responsibilities: - **Authentication** — `apiKeyOrUserAuthToken()` prefers a Firebase bearer token from `SvCredentialManager`, falling back to a stored API key. This allows both direct API access during development and proxied access through Firebase Functions in production. - **Model registry** — Each service defines a `modelsJson()` method returning its available models with context limits and capability flags (temperature, top-p, image generation, etc.). - **Request class resolution** — `chatRequestClass()` uses naming convention (`SvGeminiService` resolves to `SvGeminiRequest`) so no explicit registration is needed when adding providers. ## SvAiChatModel Represents one model offered by a service. Slots include `modelName`, `inputTokenLimit`, `outputTokenLimit`, and capability flags like `supportsTemperature`, `supportsTopP`, and `supportsImageGeneration`. The `Services` node exposes helper methods to query models across all providers: `chatModels()`, `chatModelNames()`, `chatModelWithName()`. ## SvAiRequest The base HTTP request wrapper for API calls. Manages streaming responses via a delegate protocol: - `onRequestBegin` / `onRequestComplete` / `onRequestError` - `onStreamStart` / `onStreamData` / `onStreamEnd` Each AI service has a paired request subclass (e.g. `SvAnthropicRequest`, `SvGeminiRequest`) that handles provider-specific message formatting, headers, and response parsing. This is the only class that needs to know about the provider's wire format. ## Conversations and Messages `SvAiConversation` extends a generic `SvConversation` base class, adding AI-specific state: the selected chat model, token tracking, response message class, and tool call management. `SvAiMessage` extends `SvConversationMessage` with role support (system, user, assistant), where role names are mapped to each provider's conventions by the service. ## Tool Calling The `SvAssistantToolKit` coordinates structured function calling between AI models and application code: - **`SvToolDefinitions`** — A registry of available tools, each defined with a name, description, typed parameters, and return types. - **`SvToolCalls`** — A queue of pending and completed invocations from the AI. - **Execution timing** — Tools can run during streaming (real-time effects like sound), after response completion (state changes), or during narration playback (synchronized audio effects). Tools are defined on model objects using the slot/method system: ```javascript const tool = this.methodNamed("rollDice"); tool.setDescription("Roll dice for a check"); tool.addParameter("notation", "String", "Dice notation like 2d6+3"); tool.setIsToolable(true); ``` The AI invokes tools via structured tags in its response. Results are collected and — for non-silent tools — sent back to the AI in a follow-up message. ## Response Parsing `SvAiParsedResponseMessage` handles structured tag extraction from AI responses. Tags like ``, ``, ``, and `` are parsed incrementally during streaming, enabling real-time processing before the full response arrives. A voice narration category routes speakable content to the TTS pipeline as it's parsed. ## Prompt Composition `SvAiPromptComposer` assembles prompts from modular template files using three substitution patterns: - `{{file$FileName.txt}}` — Include the contents of another file (recursive) - `{{$methodName}}` — Call a method on the target object and insert the result - `{{$tableOfContents}}` — Auto-generate a table of contents from headings This allows complex system prompts to be built from reusable, domain-specific components without string concatenation in code. ## Providers Each AI provider is implemented as a service/request pair. The service defines available models and authentication; the request handles the provider's specific API format. | Service | Provider | Key Capabilities | |---|---|---| | `SvAnthropicService` | Anthropic | Claude models, tool calling with Anthropic-specific schema | | `SvGeminiService` | Google | Gemini models, text-to-video generation | | `SvOpenAiService` | OpenAI | GPT models, DALL-E image generation, text-to-speech, style transfer | | `SvImagineProService` | ImaginePro | Midjourney image generation via API | | `SvGroqService` | Groq | Fast inference with OpenAI-compatible API | | `SvDeepSeekService` | DeepSeek | Code-focused models | | `SvXaiService` | xAI | Grok models with OpenAI-compatible API | ## Adding a New AI Provider Adding a provider requires two classes and one registration: 1. **`NewProviderService`** extending `SvAiService` — define `modelsJson()` with available models, set the endpoint URL, and configure role name mappings if they differ from the defaults. 2. **`NewProviderRequest`** extending `SvAiRequest` — implement `buildBody()` for the provider's message format and `readDataChunk()` for streaming response parsing. 3. **Register** the service as a slot in `Services.initPrototypeSlots()`. The naming convention (`NewProviderService` resolves to `NewProviderRequest`) handles the wiring automatically. If the provider uses an OpenAI-compatible API, the request class can be minimal. --- Source: /docs/Services/Cloud%20Storage/ # Cloud Storage Firebase integration for cloud persistence and file storage. ## SvFirebaseService Integrates with Firebase for cloud persistence and file storage. Unlike AI services, `SvFirebaseService` extends `SvSummaryNode` directly and contains two sub-services: - **`SvFirestoreDatabaseService`** — Document and collection access via Firestore. Provides query building, real-time listeners, and CRUD operations through Strvct node wrappers (`SvFirestoreNode`, `SvFirestoreQuery`). - **`SvFirebaseStorageService`** — File upload and download via Firebase Storage, with permission management. --- Source: /docs/Services/Home%20Assistant/ # Home Assistant IoT device control via Home Assistant's WebSocket API. ## Overview The Home Assistant integration maps an entire Home Assistant instance — areas, devices, entities, and states — into the Strvct node graph. The result is a navigable, inspectable tree of your smart home that uses the same UI patterns as the rest of the framework. No custom views are involved; the generated interface lets you browse areas, drill into devices, see entity states, and inspect raw JSON from HA — all from slot annotations. Multiple Home Assistant instances are supported. Each connection is configured with a host, port, and long-lived access token, all editable through the generated inspector. ## Connection Communication uses the Home Assistant WebSocket API. Because browsers disallow plain `ws://` connections from HTTPS pages, a small WSS-to-WS proxy relays traffic between the browser and HA's local WebSocket endpoint. ### Authentication Flow 1. Browser opens a secure WebSocket connection to the proxy 2. HA sends `auth_required` 3. Client responds with the stored long-lived access token 4. HA replies `auth_ok` or `auth_invalid` 5. On success, a full data refresh begins All subsequent requests use a sequential integer ID with a promise-based response pattern — each outgoing message gets a unique ID, and incoming results are matched to their pending promise by that ID. ## Data Model After authentication, the integration fetches four registries in parallel: | Group | HA WebSocket Command | Node Class | |---|---|---| | Areas | `config/area_registry/list` | `SvHomeAssistantArea` | | SvDevices | `config/device_registry/list` | `SvHomeAssistantDevice` | | Entities | `config/entity_registry/list` | `SvHomeAssistantEntity` | | States | `get_states` | `SvHomeAssistantState` | Each registry response is an array of JSON objects. For each entry, a corresponding Strvct node is created and registered in an ID map for O(1) lookup. ### Object Wiring After all four registries are loaded, a wiring pass connects objects into a hierarchy based on their foreign keys: - **SvDevices** attach to **Areas** via `area_id` - **Entities** attach to **SvDevices** via `device_id` - **States** attach to **Entities** via `entity_id` The result is a tree rooted at a `SvHomeAssistantFolder` node (titled "regions") that mirrors the physical layout of the home: ``` Home Assistant └── regions ├── Living Room (area) │ └── HomePod (device) │ └── volume (entity) │ └── standby (state) ├── Kitchen (area) │ └── ... └── ... ``` ### Display Names Each object computes a short display name by stripping its parent's name as a prefix. For example, a device named "Living Room Speaker" under the "Living Room" area displays as "Speaker". This keeps the tree compact without losing context. ## Class Hierarchy ``` SvHomeAssistants — Collection of HA connections └── SvHomeAssistant — One connection instance ├── rootFolder — Navigable UI tree (regions) ├── SvHomeAssistantAreas ─┐ ├── SvHomeAssistantDevices │ Data groups (hidden, ├── SvHomeAssistantEntities │ used for ID lookup) └── SvHomeAssistantStates ─┘ SvHomeAssistantGroup — Base for the four registry groups ├── SvHomeAssistantAreas ├── SvHomeAssistantDevices ├── SvHomeAssistantEntities └── SvHomeAssistantStates SvHomeAssistantObject — Base for individual HA items ├── SvHomeAssistantArea ├── SvHomeAssistantDevice ├── SvHomeAssistantEntity └── SvHomeAssistantState ``` The four data group nodes (`areasNode`, `devicesNode`, `entitiesNode`, `statesNode`) are hidden from the UI — they exist for ID-based lookup during the wiring pass. The visible navigation tree is the `rootFolder`, which is populated with area nodes after all data is loaded and wired. --- Source: /docs/Services/Media/ # Media YouTube audio playback and browser speech recognition. ## SvYouTubeService Manages YouTube-based audio playback via the IFrame Player API. The service contains a `SvYouTubeAudioPlayer` subnode that wraps a hidden YouTube player element. ### SvYouTubeAudioPlayer The core playback class. Handles player lifecycle, volume, looping, and state tracking. The YouTube IFrame API script is loaded lazily on first use, and playback waits for a user gesture (browser autoplay policy). ```javascript // Create a player and loop background music const player = SvYouTubeAudioPlayer.clone(); player.setTrackName("Ambient Forest"); player.setVideoId("abc123xyz"); player.setShouldRepeat(true); player.setVolume(0.1); player.play(); // Control player.stop(); player.togglePlay(); player.isPlaying(); // => boolean player.stateName(); // => "playing" | "paused" | "buffering" | "ended" // One-shot playback (resolves when video ends) player.setShouldRepeat(false); await player.play(); // Cleanup await player.shutdown(); // destroys the YT.Player and removes the DOM element ``` ### SvMusicLibrary A higher-level layer that manages playlists of `SvMusicTrack` nodes loaded from a JSON resource file. Owns two `SvYouTubeAudioPlayer` instances — one for background music (low volume), one for sound effects (higher volume). ```javascript // Play looping background music by track name library.playTrackWithName("Mystical Forest"); // Play a one-shot sound effect await library.playSoundEffectWithName("Sword Clash"); // Stop background music library.musicPlayer().stop(); // Lookup library.trackWithName("Battle Theme"); // => SvMusicTrack library.playlists(); // => SvMusicFolder[] library.trackNames(); // => String[] ``` Playlist data is a JSON object mapping folder names to track dictionaries: ```json { "Fantasy": { "Mystical Forest": "YouTubeVideoId1", "Battle Theme": "YouTubeVideoId2" }, "Sound FX": { "Sword Clash": "YouTubeVideoId3" } } ``` ### SvMusicTrack Represents a single track with a YouTube video ID. Supports a delegate set for playback notifications: ```javascript track.addDelegate(myObserver); await track.play(); track.stop(); ``` Delegates receive `onSoundStarted(track)` and `onSoundEnded(track)`. ## SvSpeechToTextSessions Wraps the browser's native `SpeechRecognition` API for voice input. Each `SvSpeechToTextSession` tracks recognition state, language settings, and transcription results. Not a cloud service — all processing happens in the browser. ### SvSpeechToTextSession ```javascript const session = SvSpeechToTextSession.clone(); session.setDelegate(myObject); session.setLanguage("en-US"); session.setIsContinuous(true); session.setGetInterimResults(true); session.setInputTimeoutMs(1500); // Start — returns a promise that resolves with the transcript // when the input timeout fires (silence detected after speech) const text = await session.start(); // Manual stop session.stop(); // State session.isRecording(); session.fullTranscript(); // accumulated text session.interimTranscript(); // current partial text ``` ### Delegate Protocol All methods are optional. The session is passed as the first argument. | Method | When | |---|---| | `onSpeechInterimResult(session)` | Partial result received | | `onSpeechFinal(session)` | Finalized segment received, appended to `fullTranscript` | | `onSpeechEnd(session)` | Browser speech recognition ended | | `onSpeechInput(session)` | Input timeout elapsed — transcript is final | | `onSessionEnd(session)` | Recognition service disconnected | | `onSpeechError(session, error)` | Recognition error | ### Usage in SvTextView Any text field supports voice input via Alt+L. Holding the key starts a session; releasing it stops recognition and inserts the transcript at the cursor: ```javascript startSpeechToText () { this._speechSession = SvSpeechToTextSession.clone().setDelegate(this); this._speechSession.start(); } onSpeechEnd (speechSession) { const s = speechSession.fullTranscript(); this.insertTextAtCursorSimple(s); this._speechSession = null; } ``` --- Source: /docs/Services/Proxies/ # Proxies Client-side proxy configuration for routing API calls through intermediary servers. ## Overview These classes do not run proxy servers — they are client-side configuration nodes that store proxy server connection details and rewrite API URLs to route through a proxy. The actual proxy server (e.g. a Firebase Function or local HTTPS reverse proxy) runs separately. The primary use case is routing AI service API calls through a proxy that holds the real API keys, so that browser clients never see provider credentials directly. The client sends a Firebase user auth token to the proxy, and the proxy adds the appropriate API key before forwarding the request. ## SvProxyServers `SvProxyServers` is a singleton collection of `SvProxyServer` entries, accessible via `SvProxyServers.shared()`. In practice, a single `SvDefaultProxyServer` entry handles all routing, accessible via `SvProxyServers.shared().defaultServer()`. Users can add, remove, and reorder proxy entries through the generated inspector UI. All entries are persisted to IndexedDB. ## SvProxyServer Each `SvProxyServer` node stores the connection details for one proxy endpoint: | Slot | Type | Default | Purpose | |---|---|---|---| | `isSecure` | Boolean | `true` | Use `https` or `http` | | `subdomain` | String | `""` | Optional subdomain prefix | | `domain` | String | `""` | Base domain (e.g. `localhost`, `example.com`) | | `port` | Number | `0` | Port number (0 or null omits port from URL) | | `path` | String | `""` | URL path (e.g. `/proxy`) | | `parameterName` | String | `null` | Query parameter name for the target URL | | `isDisabled` | Boolean | `false` | When true, returns target URLs unchanged | All configuration slots are persisted and editable through the generated inspector. ### URL Rewriting The core method is `proxyUrlForUrl(targetUrl)`, which rewrites a direct API URL into a proxied one: ``` Target: https://api.openai.com/v1/chat/completions Proxied: https://localhost:8443/proxy?proxyUrl=https%3A%2F%2Fapi.openai.com%2Fv1%2Fchat%2Fcompletions ``` The target URL is passed as a query parameter (named by `parameterName`), so the proxy server can decode it, forward the request to the real provider, and stream the response back. If `isDisabled` is true, the target URL is returned unchanged — useful for direct API access during development. ## SvDefaultProxyServer `SvDefaultProxyServer` extends `SvProxyServer` and auto-configures itself from the browser's current page URL on startup. For a page served at `https://localhost:8443`, it defaults to: - `isSecure`: `true` - `hostname`: `localhost` - `port`: `8443` - `path`: `/proxy` - `parameterName`: `"proxyUrl"` This means the proxy is assumed to run on the same origin as the application by default. Applications can override these settings programmatically via `setupForConfigDict()` using build-time environment configuration. ## Integration with AI Services `SvAiRequest` has a `needsProxy` slot (default `true`). When making an API call, the request checks this flag and rewrites the URL if needed: ```javascript async activeApiUrl () { let url = this.apiUrl(); // e.g. "https://api.openai.com/v1/chat/completions" if (this.needsProxy()) { url = SvProxyServers.shared().defaultServer().proxyUrlForUrl(url); } return url; } ``` This applies to all AI service requests (chat, image generation, text-to-speech, video generation) and also to image/media downloads that would otherwise be blocked by CORS in production. --- Source: /docs/Slots/ # Slots The property system: declaration, annotations, and the most common slot settings. ## Overview Slots are STRVCT's property system. A property declared as a slot is more than a value holder — it carries metadata that each framework layer (UI, storage, validation, JSON schema) reads independently. That metadata is what lets the framework auto-generate forms, persist state, sync views, and produce JSON schemas without per-property glue code. Two things often surprise newcomers: the auto-generated setter **type-checks values at runtime** against the declared slot type (warning and attempting recovery on mismatch), and slots can hold **weak references** via `newWeakSlot`, which is the standard way to express back-references and non-owning pointers without creating retain cycles. Both are covered in detail below. ## Declaration Slots are declared in a class's `initPrototypeSlots()` method. Each declaration creates a private instance variable (`_slotName`), an auto-generated getter (`slotName()`), and an auto-generated setter (`setSlotName(value)`). ```javascript initPrototypeSlots () { { const slot = this.newSlot("userName", ""); slot.setSlotType("String"); slot.setShouldStoreSlot(true); slot.setSyncsToView(true); slot.setCanEditInspection(true); } } ``` The block-scope pattern (`{ const slot = ... }`) is a convention that keeps each slot's configuration visually grouped and avoids name collisions across slots in the same method. ### Constructors - `newSlot(name, initialValue)` — the standard constructor. - `newWeakSlot(name, initialValue)` — creates a slot that holds a `WeakRef` to its value. The auto-generated getter unwraps the ref; the auto-generated setter wraps it. Useful for back-references (child → parent) and caches where you don't want to prevent garbage collection. - `newSubnodeFieldSlot(name, ProtoClass)` — convenience for subnode-field slots (see below). Available on `SvJsonGroup` and descendants. - `overrideSlot(name, initialValue)` — overrides a slot inherited from a parent class, preserving its configuration while changing the default value. ### Auto-generated getters A plain getter returns the private ivar directly, but the framework first calls an optional `willGetSlotSlotName()` hook on the owning object if one is defined. This is the mechanism behind on-demand initialization for specific slots without having to subclass the setter. For **weak slots**, the getter dereferences the underlying `WeakRef` transparently. If the target has been garbage-collected, the getter returns `undefined`. ### Auto-generated setters The setter generated for a typed slot validates the incoming value against `slotType()`. If the value doesn't match, the setter logs a warning and attempts to recover — converting `Number` → `String` or `String` → `Number` where possible, falling back to the slot's initial value otherwise. Validation is on by default (`validatesOnSet` defaults to `true`); disable it with `slot.setValidatesOnSet(false)` when you need to store runtime values that intentionally don't match the declared type. Setters also post a `didUpdateSlot` notification when the value changes, which is what drives view synchronization, persistence, and observers. ## Core annotations ### Type ```javascript slot.setSlotType("String"); ``` The type name is a string — either a primitive name (`"String"`, `"Number"`, `"Boolean"`, `"Map"`, `"Set"`, `"Array"`) or a class name (`"UoCharacter"`, `"SvNode"`). The framework uses this for: - **Runtime validation** — the auto-generated setter rejects values of the wrong type. - **UI generation** — the inspector picks an appropriate field widget (text input, number stepper, toggle, etc.). - **JSON schema export** — translated to the corresponding JSON Schema type. - **Persistence** — native collections (`Array`, `Map`, `Set`, `Object`, `ArrayBuffer`, `TypedArray`) are serialized automatically based on their declared type. Type annotation is recommended for every slot. Debug builds assert that every prototype slot has a declared type. ### Persistence ```javascript slot.setShouldStoreSlot(true); ``` Marks the slot as part of the object's persistence record. The object's class must also opt in via `setShouldStore(true)` in `initPrototype()`. When a stored slot changes, its owning object is added to the dirty set and committed at the end of the event loop. - `setShouldJsonArchive(true)` — include the slot when the object is exported to JSON (separate from internal persistence format). - `setIsInJsonSchema(true)` — include the slot in generated JSON schemas. Use for slots that should appear to AI assistants and external schema consumers. - `setIsInCloudJson(true)` — include the slot when the object is serialized for cloud sync. ### View synchronization ```javascript slot.setSyncsToView(true); ``` When the slot's value changes, schedule the owning node's view to re-sync. The sync scheduler batches and deduplicates updates, so multiple slot changes in the same event loop produce a single view refresh. ### Inspector and editing ```javascript slot.setCanEditInspection(true); ``` Exposes the slot as an editable field in the auto-generated inspector. Related annotations: - `setLabel("Display Name")` — override the auto-humanized label. - `setLabelToCapitalizedSlotName()` — use the capitalized slot name as the label. - `setDescription("Explanatory text")` — inspector hint text and JSON schema description. - `setValuePlaceholder("Type your name")` — placeholder text for empty string fields. - `setIsReadOnly(true)` — show the value but disable editing. - `setIsRequired(true)` — mark as required for validation and schema export. ### Auto-initialization ```javascript slot.setFinalInitProto(SettingsNode); ``` During `finalInit()`, if the slot's value is still its initial value (usually `null`), the framework creates a new instance of the given class and assigns it. This runs after storage-loaded values are populated, so `setFinalInitProto` doesn't overwrite loaded objects — it only fills in missing children. Use for nested sub-structures that should always exist. ### Validation ```javascript slot.setValidValues(["small", "medium", "large"]); ``` - `setValidValues(array)` — restrict the value to one of the listed options. Produces a popup/dropdown in the inspector and a JSON Schema `enum`. - `setValidItems(itemsArray)` — like `validValues` but for dictionary items (each item has a label and a value). - `setJsonSchemaPattern(regex)` — string pattern constraint for schema validation. ## Subnode-related annotations STRVCT has two distinct ways of exposing child objects in the UI. Choosing between them is one of the most important slot-design decisions. ### Stored subnodes ```javascript slot.setIsSubnode(true); ``` The slot's value is treated as a persistent child node in the parent's `subnodes` array. The framework manages the parent/child relationship automatically — setting `ownerNode`, inserting into `subnodes`, and handling removal on clear. This is the right choice when the child has independent identity and lifecycle (a user-authored item in a list). Most commonly used implicitly, through `SvJsonArrayNode`-style collections where each element is a subnode. ### Subnode fields ```javascript slot.setIsSubnodeField(true); ``` The slot's value is still stored in the slot itself (not in the subnodes array), but the inspector presents it as a navigable tile. Clicking drills into the child object. The field is ephemeral UI structure; the data lives in the slot. Use this for structured but always-present children (a `SettingsObject` that's part of every instance of the parent, or a `Stats` sub-record). The parent typically sets `shouldStoreSubnodes(false)` because the real data is in the slots, not the subnodes array. `newSubnodeFieldSlot(name, ProtoClass)` is a convenience that applies the common settings together: ```javascript const slot = this.newSubnodeFieldSlot("settings", SettingsObject); // equivalent to: newSlot + setFinalInitProto + setIsSubnodeField // + setShouldStoreSlot(true) + setShouldJsonArchive(true) ``` ### Quick comparison | | Subnode (`setIsSubnode`) | Subnode field (`setIsSubnodeField`) | |-------------------------|--------------------------------------|-------------------------------------| | Data location | Owner's `subnodes` array | The slot itself | | Typical use | Collections of user-authored items | Structured sub-records | | Parent config | `shouldStoreSubnodes(true)` | `shouldStoreSubnodes(false)` | | Identity | Independent lifecycle | Always present as part of parent | ## Weak slots ```javascript const slot = this.newWeakSlot("parentRef", null); // or: slot.setIsWeak(true); ``` A weak slot holds a `WeakRef` instead of a direct reference. The getter transparently dereferences; if the target has been collected, the getter returns `null` (or `undefined`). Use cases: - **Back-references** (`childNode.parentRef()` → parent) where a strong ref would create a cycle that keeps nodes alive after their containing tree is released. - **Caches and observation targets** where you want the reference to disappear automatically when the target is no longer used elsewhere. Weak slots participate in the framework's `FinalizationRegistry`-based cleanup: when the referenced object is collected, `onFinalizedSlot()` is called on the owning object. ## Common recipes Quick-reference for the most frequent slot configurations. These compose freely — a slot can be stored, view-synced, JSON-visible, and cloud-synced all at once. ### Stored property with view sync ```javascript { const slot = this.newSlot("hitPoints", 0); slot.setSlotType("Number"); slot.setShouldStoreSlot(true); slot.setSyncsToView(true); slot.setCanEditInspection(true); } ``` ### Transient (non-stored) property Runtime-only state that resets on reload. Not persisted, not shown in the inspector. ```javascript { const slot = this.newSlot("isLoading", false); slot.setSlotType("Boolean"); } ``` ### Read-only display A computed or system-managed value shown in the inspector but not editable. ```javascript { const slot = this.newSlot("createdAt", null); slot.setSlotType("String"); slot.setShouldStoreSlot(true); slot.setCanEditInspection(true); slot.setIsReadOnly(true); } ``` ### JSON-visible property A slot included in JSON serialization for AI consumption or data exchange, with a schema description. ```javascript { const slot = this.newSlot("characterClass", "Fighter"); slot.setSlotType("String"); slot.setShouldStoreSlot(true); slot.setIsInJsonSchema(true); slot.setShouldJsonArchive(true); slot.setDescription("The character's class (e.g., Fighter, Wizard, Rogue)"); } ``` ### Cloud-synced property A slot included when the object is serialized for cloud storage. ```javascript { const slot = this.newSlot("lastModified", null); slot.setSlotType("Number"); slot.setShouldStoreSlot(true); slot.setIsInCloudJson(true); } ``` ### Collection (stored subnodes) A variable-length list of child objects. Each child is a subnode with independent identity. ```javascript // In initPrototypeSlots(): { const slot = this.newSlot("subnodes", null); slot.setShouldStoreSlot(true); } // In initPrototype(): this.setShouldStoreSubnodes(true); this.setSubnodeClasses([ItemNode]); // accepted child types ``` Classes extending `SvJsonArrayNode` get this behavior by default. ## Full reference The `Slot` class lives in `source/library/ideal/proto/Slot.js` and defines many more settings than are covered here — most application code will only use the ones above. Additional categories include: - **Layout hints** — `setNodeFillsRemainingWidth`, `setKeyIsVisible`, `setValueWhiteSpace`, `setValueAllowsHtml`. - **JSON schema refinement** — `setJsonSchema`, `setJsonSchemaItemsType`, `setJsonSchemaItemsRef`, `setJsonSchemaItemsDescription`, `setJsonSchemaItemsIsUnique`, `setExamples`. - **Lifecycle / metadata** — `setAnnotation`, `setDuplicateOp`. For the complete list, read `Slot.js` directly — the file is heavily commented and organized by category. --- Source: /docs/Technical%20Overview/ # Technical Overview High-level architecture and key concepts of the Strvct framework. ## Introduction Strvct applications run as client-side single-page apps in the browser. The framework makes heavy use of client-side persistent storage — both for caching code and resources via a content-addressable build system, and for maintaining a persistent object database of application state in IndexedDB. Subgraphs of this object database can be transparently and lazily synced to the cloud, allowing offline-first operation with seamless cloud persistence. Strvct is not a template system, a compile-time UI generator, or a component framework in the React/Flutter sense. There is no build step that produces views, no static component tree, and no pre-rendered layout. Views are created lazily at runtime as the user navigates the object graph — each navigation step inspects the target node's annotations, discovers an appropriate view class, and instantiates it. Once created, a view stays live and in sync with its model node through bidirectional notifications. The UI at any moment is a dynamic projection of the user's current navigation path, not a pre-built artifact. This page covers the key concepts: the domain model, storage, UI synchronization, and the capabilities these enable. For the design rationale, see [Naked Objects](../Naked%20Objects/index.html). For implementation details, see the [Implementation Overview](../Implementation%20Overview/index.html). ## Domain Model The domain model is a cyclic graph of domain objects. Each domain object has: - **Properties** (instance variables) and **actions** (methods) - a `subnodes` property containing an ordered unique collection of child domain objects - a `parentNode` property pointing to its parent domain object - property **annotations** [1] which allow for the automatic handling of UI and storage mechanisms - `title` and `subtitle` properties - a unique ID The domain model can be seen as an ownership tree of domain objects, which may also contain non-ownership links between nodes. ### Collection Managers Complex collections of domain objects use Collection Manager domain objects to encapsulate collection-specific logic and data. For example, a Server class might have a guestConnections property referencing a GuestConnections instance whose subnodes are GuestConnection instances. ### Indirect UI Coupling The domain model operates independently of UI, allowing for "headless" execution. It can, however, use annotations to provide optional UI hints without direct coupling. This is possible because model objects hold no references to UI objects and can only communicate with them via notifications. ## Storage ### Annotations Domain objects have a property which determines whether the object is persisted, as well as property annotations which determine which properties are persisted. Using this information, the system automatically manages persistence. ### Transactions Mutations on persistent properties auto-queue domain objects for storage. Queued objects are bundled into a transaction committed at the end of the current event loop. ### Garbage Collection Automatic garbage collection of the stored object graph occurs on startup, or when requested. Only objects reachable from the root domain object remain after garbage collection. ### Native Collections Native JavaScript collections (Array, ArrayBuffer, Map, Object, Set, and TypedArray) referenced by persistent properties of domain objects are also automatically persisted in their own records. ### Local Storage Persistent domain objects are stored client side in IndexedDB in a single Object Store of records whose keys are the domain object unique ID and values are the domain objects' JSON records. ## Resource Delivery A naked objects application leans on having the full domain model present in the client. Real apps built this way run to hundreds of classes, plus view code, icons, and style resources. Standard JavaScript delivery mechanisms don't scale gracefully to that operating point: - Loading ES modules individually issues one request per file; cold starts degrade linearly with class count. - Bundlers collapse many files into one, but any change to any file invalidates the entire chunk. Code splitting helps but requires manual chunking and still invalidates per chunk. - HTTP caches key on URL. Because deployments change URLs — via versioned filenames or query strings — clients re-download bytes they already have. - None of the above deduplicates identical code that appears at different paths. STRVCT replaces this with a content-addressable build. Every resource is keyed by the SHA-256 hash of its content. The build produces a small manifest and a bundle indexed by hash. At runtime: - The manifest is fetched first and checked against a persistent client-side hash cache. - Any content already in the cache — from a previous load, a previous deployment, or a different STRVCT app on the same origin — is not re-fetched. - When enough content is missing to justify it, the app downloads the whole bundle in one request; when only a little is missing, it fetches those items directly. The threshold adapts to the actual delta. - Identical content at different paths is stored and transferred once. The practical behavior this produces is qualitatively different from what npm plus a bundler can deliver. A redeploy that changes a single file costs roughly that file's bytes, not a chunk's. A returning user after a year of development cycles fetches only content that is actually new. A first-time visitor who has used any other STRVCT app gets the shared framework for free. None of these properties are reachable by configuration on top of the mainstream stack; they require content-level identity, which URL-based caching doesn't have. This is what lets STRVCT keep its "usability gap" closed at the data-model scales it targets, where conventional tooling begins to impose latency and cache-invalidation costs that the framework's design intentionally eliminates. ## UI Synchronization Model-view synchronization is managed by views, which either pull or push changes to the domain objects they are presenting. Views push changes when a view property changes, and pull changes from domain objects when those objects post change notifications. Only annotated properties trigger sync operations. Both directions are coalesced and sent at the end of the event loop. ### Sync Loop Avoidance Bidirectional sync stops automatically as property changes trigger sync operations only when values actually differ, preventing infinite loops. If secondary changes do occur, the notification system detects the loop, halts it, and identifies the source. ### Reference Loop Avoidance Observations use weak references, allowing garbage collection of both posters and listeners. The Notification system automatically removes observations when the listener is collected. ## Capabilities The naked objects pattern enables several capabilities that would require significant per-component effort in a bespoke-view framework. ### Themes Themes can be used to customize the appearance of the UI. Domain objects can also request object-specific styles to be applied to them. ### Importing and Exporting Drag and drop of domain objects into the UI and out of it for export is supported. Domain objects can register which MIME types they can be exported to and imported from. For example, a domain object can be dragged out of one browser window onto a user's desktop, or dropped into another Strvct app that accepts that MIME type. Domain objects have a standard property which lists valid subnode types, and this can be used to validate drops and auto-generate subnodes for imported data. ### Internationalization Because the framework controls the model-to-view pipeline, it can intercept slot values at the view boundary and translate them transparently — no per-string wrapping required. This centralization also makes AI-powered translation viable. The framework can automatically discover every translatable string in the application by walking the model, and include semantic context with each string to improve translation quality. See the [Internationalization](../Internationalization/index.html) guide for details. ### JSON Schema Domain objects can automatically generate JSON Schema for themselves based on their properties and annotations. These schemas can be used to export metadata about the domain model, which is particularly useful when interacting with Large Language Models. [1]: https://bluishcoder.co.nz/self/transporter.pdf "David Ungar. (OOPSLA 1995). Annotating Objects for Transport to Other Worlds. In Proceedings of the Tenth Annual Conference on Object-Oriented Programming Systems, Languages, and Applications (OOPSLA '95). Austin, TX, USA. ACM Press." --- Source: /docs/Views/ # Views The view layer: auto-generated UI, custom views, and programmatic styling. The entire view system is built from a small number of composable elements: a node, a tile, a column, and a master-detail split. These four pieces nest recursively — a column of tiles where selecting a tile opens another column — so the same structure that displays a flat list also handles arbitrarily deep, mixed-orientation navigation without any additional wiring. The view hierarchy wraps DOM elements rather than extending them. This is deliberate: the DOM API is large and mutable, and subclassing it tightly couples framework code to browser internals that change across engines and versions. Wrapping keeps the boundary clean — each layer in the class chain adds exactly one capability (visibility, event listening, gestures, layout, styling), so any layer can be understood, tested, or replaced independently. - Auto-Generated Views - Custom Views - Programmatic Styling - Themes and Style States --- Source: /docs/Views/Auto-Generated%20Views/ # Auto-Generated Views How the view layer connects to model nodes, synchronizes state, and provides navigation — without writing any view code. ## Overview The defining feature of a naked objects framework is that the UI is derived from the model. In STRVCT, every node automatically gets a visual representation: the framework discovers the right view class, creates instances, wires up observation, and keeps the view synchronized as the model changes. Application developers define model classes and get a fully navigable, editable UI for free. ## SvDomView Hierarchy Rather than subclassing DOM elements directly, STRVCT wraps them. Each layer in the view class hierarchy adds one capability: - **`SvElementDomView`** — Wraps a DOM element. Manages creation, type, and class attributes. - **`SvVisibleDomView`** — Visibility toggling. - **`SvResponderDomView`** — Responder chain for keyboard and mouse event routing. - **`SvListenerDomView`** — Event listener registration and removal. - **`SvControlDomView`** — Target/action pattern for connecting views to handlers. - **`SvSelectableDomView`** — Selection state with styled feedback. - **`SvEditableDomView`** — Content editability and edit mode. - **`SvGesturableDomView`** — Gesture recognizers: tap, double-tap, pan, long-press, pinch. - **`SvFlexDomView`** — Flexbox layout: direction, wrap, grow, shrink, basis, alignment. - **`SvStyledDomView`** — Named style states (unselected, selected, active, disabled) with theme support. - **`SvSubviewsDomView`** — Parent/child view hierarchy. - **`SvCssDomView`** — CSS variable application from dictionaries. - **`SvDomView`** — Combines all layers above. - **`SvNodeView`** — Adds model-to-view synchronization. The primary class for application views. ## SvNodeView `SvNodeView` is the bridge between model nodes and the DOM. It holds a reference to an `SvNode`, observes change notifications, and manages the subview lifecycle. - `setNode(aNode)` — Assigns a node and begins observation via `SvNotificationCenter`. - `syncFromNode()` — Diffs visible subnodes against current subviews: creates, reuses, or removes as needed. - `subviewProtoForSubnode(aSubnode)` — Resolves which view class to use (see View Resolution). - `syncCssFromNode()` — Applies CSS variables defined on the node to the DOM element. ## View Resolution The framework discovers view classes by naming convention: append `"View"` or `"SvTile"` to the node's class name. 1. Check for an explicit `nodeViewClassName` override on the node. 2. Walk the node's class hierarchy from most-derived to base. 3. For each ancestor, look up `ClassName + "View"`. 4. Return the first match. For a `MySpecialNode` extending `SvStorableNode`, the lookup tries `MySpecialNodeView`, then `SvStorableNodeView`, and so on. No registration required — just name the view class to match. ## Synchronization `SvSyncScheduler` batches and coordinates all synchronization between nodes and views. **Model to view**: A slot change triggers `didUpdateSlot()` on the node, which posts an `onUpdatedNode` notification. Observing views schedule a sync at priority 2, executed at the end of the event loop. **View to model**: A user edit triggers `scheduleSyncToNode()` at priority 0 (higher than model-to-view). The view calls the node's setter, which posts its own change notification, completing the cycle. The scheduler coalesces duplicate calls, detects sync loops, and can be paused during bulk operations. Slot setters only fire hooks when the value actually changes, preventing redundant cascades at the source. ## Navigation ### SvStackView `SvStackView` is the single recursive building block of all navigation. It splits into two regions: - **`SvNavView`** (master) — A column with a header, a scrollable `SvTilesView`, and a footer. - **`otherView`** (detail) — The content of the selected item — typically another `SvStackView`. Selecting a tile creates a new `SvStackView` in the detail area for the selected node's subnodes. This is the entire navigation mechanism: the same master-detail split, repeated at every level. **Orientation** is set per node — `"right"` for horizontal (master left, detail right) or `"down"` for vertical (master top, detail below). Mixing orientations at different depths produces horizontal Miller Columns, vertical drill-down, or hybrids, all from the same structure. **Compaction**: When nested columns would exceed the viewport, upper columns collapse automatically. Navigating back re-expands them. Tiles along the selection path are highlighted; the focused tile gets a distinct highlight. ### SvBrowserView `SvBrowserView` is the top-level container — a `SvStackView` whose detail area contains another `SvStackView`, whose detail area contains another, to any depth. It adds a breadcrumb header that auto-compacts on narrow viewports, replacing truncated segments with a back arrow. - `navigateToNode(aNode)` — Navigate directly to a node. - `selectNodePathArray(pathArray)` — Set the navigation path programmatically. ## SvTilesView `SvTilesView` is the scrollable column that displays one `SvTile` per visible subnode. It handles selection, keyboard navigation (arrow keys, Enter, Escape), long-press reordering, pinch gestures, and drag-and-drop between columns. Layout direction follows the parent `SvStackView`. ## Tiles Tiles are the items in a navigation column. All extend `SvNodeView`. **`SvTile`** — The base class. Provides selection feedback, slide-to-delete (an inner `contentView` slides to reveal action buttons beneath), long-press reorder, and a style cascade that checks the node, then the tile, then the parent column. **`SvTitledTile`** — The default. Displays title, subtitle, and note from `SvSummaryNode`. **`SvHeaderTile`** — A non-selectable section header. **`SvBreadCrumbsTile`** — A breadcrumb path that auto-compacts to fit. ## Field Tiles Field tiles present individual slot values as editable key/value rows. The base `SvFieldTile` has four regions: key, value, error, and note. | Class | Purpose | |-------|---------| | `SvStringFieldTile` | Single-line text input | | `SvTextAreaFieldTile` | Multi-line text editing | | `SvBooleanFieldTile` | Checkbox toggle | | `SvPasswordFieldTile` | Masked password input | | `SvActionFieldTile` | Button that invokes a node method | | `SvPointerFieldTile` | Object reference with navigation arrow | | `SvImageWellFieldTile` | Image display and drag in/out | | `SvVideoWellFieldTile` | Video display and drag in/out | Not every field type needs its own tile — `SvNumberField` uses the string tile with numeric validation. --- Source: /docs/Views/Custom%20Views/ # Custom Views How to create custom view classes when the auto-generated UI doesn't fit. ## When to Create a Custom View The auto-generated view system handles the common case: a node's slots and subnodes produce tiles, field editors, and navigation automatically. Custom views are for situations where you need a specific layout, non-standard DOM elements, or visual behavior that doesn't map to the tile-based pattern — overlays, canvas-based rendering, media players, or composite displays. ## The Basic Pattern A custom view extends `SvDomView` (or one of its ancestors) and configures itself in `init()`: ```javascript (class MyOverlayView extends SvDomView { init () { super.init(); this.setPosition("fixed"); this.setTop("0"); this.setLeft("0"); this.setWidth("100vw"); this.setHeight("100vh"); this.makeFlexAndCenterContent(); this.setBackgroundColor("rgba(0, 0, 0, 0.5)"); this.setZIndex(9999); return this; } }.initThisClass()); ``` All styling uses named property methods — there are no CSS files to manage. Every setter returns `this`, so calls chain naturally. See the Programmatic Styling page for the full API. ## Creating Child Views Child views are created with `clone()` (not `new`) and added with `addSubview()`: ```javascript init () { super.init(); this.makeFlexAndCenterContent(); const label = SvDomView.clone(); label.setInnerHTML("Hello"); label.setColor("white"); label.setFontSize("24px"); this.addSubview(label); return this; } ``` `clone()` runs the full initialization lifecycle (`init()` then `finalInit()` then `afterInit()`). Use `addSubview()` to establish the parent/child relationship — it handles both the framework's view tree and the underlying DOM. ## Connecting to a Node To make a custom view respond to model changes, extend `SvNodeView` instead of `SvDomView` and implement `syncFromNode()`: ```javascript (class MyStatusView extends SvNodeView { init () { super.init(); this._label = SvDomView.clone(); this.addSubview(this._label); return this; } syncFromNode () { super.syncFromNode(); const node = this.node(); if (node) { this._label.setInnerHTML(node.statusText()); } } }.initThisClass()); ``` `syncFromNode()` is called automatically whenever the observed node posts a change notification. The sync scheduler batches these calls so multiple rapid changes produce a single update. ## View Resolution for Custom Views The framework finds views by naming convention: for a node class `MyWidget`, it looks for `MyWidgetView`. To use a custom view for a node type, just name the class to match: - `UoCharacter` uses `UoCharacterView` (if it exists) - `UoSession` uses `UoSessionView` - Falls back to ancestor class names if no exact match You can also override on the node side with `nodeViewClassName()` to point at a different view class. ## Convenience Methods `SvFlexDomView` provides shortcuts for common layout patterns: | Method | Effect | |--------|--------| | `makeFlexAndCenterContent()` | Sets display flex, centers both axes | | `flexCenterContent()` | Centers content horizontally and vertically | | `makeStandardFlexView()` | Flex display, relative positioning, centered, overflow hidden | | `fillParentView()` | Sets width and height to 100% | | `newFlexSubview()` | Creates and adds a flex child with min dimensions set | These eliminate the most common multi-property patterns and keep `init()` methods concise. --- Source: /docs/Views/Programmatic%20Styling/ # Programmatic Styling Named CSS methods, the chainable API, and why STRVCT avoids CSS files. ## Why No CSS Files? ### The cascade is additive The CSS cascade is designed for documents: you declare rules, the browser resolves conflicts by specificity and source order, and the result is static. You can mutate individual rules through the CSSOM, but the cascade itself is additive — removing a rule doesn't restore a known-good state, it just lets some other rule win, potentially one you didn't intend. This makes the cascade unreliable for UI components whose appearance changes frequently at runtime. ### State combinatorics The deeper problem is combinatorial. Consider the independent states a single view might have: editable or read-only, selected or unselected, highlighted or not, showing an error, previously navigated, disabled. Five boolean states produce 32 combinations, and each combination may need distinct styling. In a stylesheet, each combination requires its own selector or class chain — and the count doubles with every new state. You can try to treat each state independently (`.selected` sets some properties, `.error` sets others), but that only works when the states don't interact visually. When "selected + error" needs to look different from the sum of "selected" and "error" applied separately, you're back to enumerating combinations. ### Theming compounds the problem CSS custom properties can swap values at runtime, but they don't help with the structural problem of *which properties to set in which state combinations*. When a user customizes a theme, or different regions of the UI use different themes simultaneously, every state combination now has a theme axis too. The custom properties handle the value lookup, but the combinatorial selector structure they plug into still has to be authored and maintained by hand. ### The programmatic alternative Programmatic styling sidesteps all of this. Each state applies its visual changes directly to the element, and the results compose naturally — enabling "selected" and "error" at the same time just means both sets of property changes are applied, with explicit logic for cases where they interact. Themes are dictionaries of values that can be swapped or scoped to any subtree at any time — no cascade resolution, just direct assignment. The styling logic lives alongside the view logic that drives it, so the two can't drift apart. ## Named Property Methods Every view inherits named setter/getter pairs for standard CSS properties. These are the primary styling API: ```javascript this.setBackgroundColor("rgba(0, 0, 0, 0.5)"); this.setPosition("fixed"); this.setDisplay("flex"); this.setZIndex(9999); this.setTop("0"); this.setLeft("0"); this.setWidth("100vw"); this.setHeight("100vh"); this.setFontSize("14px"); this.setColor("white"); this.setBorderRadius("8px"); this.setOverflow("hidden"); this.setPadding("16px"); this.setBoxShadow("0 2px 8px rgba(0,0,0,0.15)"); ``` Every setter returns `this`, so calls chain naturally. Each property also has a corresponding getter (e.g. `backgroundColor()`, `fontSize()`). ## Flexbox Methods `SvFlexDomView` adds named methods for every flexbox property: ```javascript this.setDisplay("flex"); this.setFlexDirection("column"); this.setJustifyContent("center"); this.setAlignItems("stretch"); this.setFlexWrap("wrap"); this.setFlexGrow(1); this.setFlexShrink(0); this.setFlexBasis("auto"); this.setGap("8px"); ``` ## Layout Convenience Methods `SvFlexDomView` also provides higher-level shortcuts for common multi-property patterns: | Method | Effect | |--------|--------| | `makeFlexAndCenterContent()` | Sets display flex, centers both axes | | `flexCenterContent()` | Centers content horizontally and vertically | | `makeStandardFlexView()` | Flex display, relative positioning, centered, overflow hidden | | `fillParentView()` | Sets width and height to 100% | | `newFlexSubview()` | Creates and adds a flex child with min dimensions set | ## Sizing Shortcuts Convenience methods for constrained sizing: | Method | Effect | |--------|--------| | `setMinAndMaxWidth(v)` | Sets min-width, max-width, and width to the same value | | `setMinAndMaxHeight(v)` | Sets min-height, max-height, and height to the same value | | `setMinAndMaxWidthAndHeight(v)` | Sets all six sizing properties at once | These are useful for fixed-size elements like icons or thumbnails where you want all constraints to agree. ## Node-Driven CSS Nodes can also request CSS properties that get applied to their view automatically. A node implements `cssVariableDict()` to return a dictionary of CSS key/value pairs: ```javascript cssVariableDict () { return { "color": this.isActive() ? "white" : "gray", "--accent-color": this.themeColor() }; } ``` During the normal sync cycle, `SvNodeView.syncCssFromNode()` picks up this dictionary and applies it to the view's DOM element. This lets model logic influence presentation without the node needing any reference to its view — the node declares what it wants, and the view system handles the rest. In general, prefer keeping styling in the view layer. Node-driven CSS is available for cases where the model genuinely determines a visual property (e.g. a user-chosen color or a status-dependent highlight), but most styling belongs in views or themes, not in model code. ## Low-Level Access For unusual CSS properties that don't have a named method, `SvCssDomView` provides generic accessors: | Method | Description | |--------|-------------| | `setCssProperty(key, value)` | Sets any CSS property by name | | `getCssProperty(key)` | Gets the computed value | | `removeCssProperty(key)` | Removes a property | These should be rare in practice — the named methods cover the vast majority of styling needs. --- Source: /docs/Views/Themes%20and%20Style%20States/ # Themes and Style States How views resolve their appearance from theme classes, style states, and node configuration. ## Overview Every view has a visual state — selected, active, disabled, or the default unselected. A theme defines what each state looks like for each kind of view. The view asks the theme "what should I look like right now?", applies the answer, and re-asks whenever its state changes. This indirection means swapping a theme changes the appearance of the entire UI without touching any view code, and different subtrees can use different themes simultaneously. ## Style States `SvStyledDomView` tracks three independent boolean properties: `isSelected`, `isActive`, and `isDisabled`. These combine into a single resolved state name with a fixed priority: 1. **disabled** — checked first, takes precedence over all others 2. **active** — the view is the current focus of interaction 3. **selected** — the view has been chosen but isn't the active focus 4. **unselected** — the default when none of the above apply When any of these properties change, the view re-resolves its state and re-applies the corresponding styles from the current theme. ## Theme Architecture The theme system is a hierarchy of named objects: - **`SvThemeResources`** — Singleton that holds the active theme. - **`SvTheme`** — A complete theme containing a set of theme classes. - **`SvThemeClass`** — Defines style values for a particular kind of view (e.g. "SvTile", "TextTile", "Header"). Theme classes form a hierarchy — a class inherits from its parent, so "TextTile" inherits defaults from "SvTile" and only overrides what differs. - **`SvThemeState`** — One state within a theme class (e.g. "selected" within "SvTile"). Contains concrete style values: color, backgroundColor, fontSize, padding, border, and dozens more. ## How a View Resolves Its Style When a view needs to apply its current style: 1. The view reads its `themeClassName` (set on the node via `SvStyledNode`, or inherited from the parent view). 2. It asks the active theme for the `SvThemeClass` matching that name. 3. It determines its current state name (`disabled`, `active`, `selected`, or `unselected`). 4. It retrieves the `SvThemeState` for that state from the theme class. 5. The theme state applies its values to the view via named setter methods (`setColor()`, `setBackgroundColor()`, etc.). Style values resolve up the theme class hierarchy — if "TextTile" doesn't define a `backgroundColor` for its "selected" state, the lookup walks up to "SvTile" and uses its value. ## Node-Side Configuration Nodes control which theme class their view uses through the `themeClassName` slot on `SvStyledNode`. This slot is marked `syncsToView`, so changing it triggers a re-style automatically. The node doesn't know anything about the visual details — it just names the class, and the theme provides the rest. ## Locked Attributes Views can lock individual style attributes via `lockedStyleAttributeSet()`. Locked attributes are skipped when a theme state applies its values, allowing a view to override specific properties that the theme would otherwise control. This is useful for views that need a fixed value regardless of state — for example, a view that must always have a transparent background. ## Switching Themes Because all style values flow through the theme, switching the active theme on `SvThemeResources` and calling `resyncAllViews()` re-styles every view in the hierarchy. No individual view needs to be updated manually. Different regions of the UI can also use different theme class names, so a single theme can provide distinct appearances for different areas (e.g. a sidebar vs. a content panel) without any special-case logic. --- Source: /Example%20App/ # Example App A complete contacts application in four classes, with zero view code. ## Overview This walkthrough builds a working contacts app from scratch to show how the pieces of STRVCT fit together. The app lets you create contacts with name, email, phone, and notes fields — all editable, navigable, and persisted to IndexedDB automatically. The entire application is four small classes and a few configuration files. ## The Model ### Contact A single contact with four fields. Each slot is annotated for storage and editing: ```javascript (class Contact extends SvStorableNode { static jsonSchemaDescription () { return "A person's contact information"; } initPrototypeSlots () { { const slot = this.newSlot("name", ""); slot.setSlotType("String"); slot.setShouldStoreSlot(true); slot.setCanEditInspection(true); slot.setLabel("Name"); } { const slot = this.newSlot("email", ""); slot.setSlotType("String"); slot.setShouldStoreSlot(true); slot.setCanEditInspection(true); slot.setLabel("Email"); } { const slot = this.newSlot("phone", ""); slot.setSlotType("String"); slot.setShouldStoreSlot(true); slot.setCanEditInspection(true); slot.setLabel("Phone"); } { const slot = this.newSlot("notes", ""); slot.setSlotType("String"); slot.setShouldStoreSlot(true); slot.setCanEditInspection(true); slot.setLabel("Notes"); } } initPrototype () { this.setShouldStore(true); this.setNodeCanEditTitle(true); this.setTitle("New Contact"); this.setNodeCanInspect(true); } // The tile in the contact list shows the name as title and email as subtitle title () { return this.name() || "New Contact"; } subtitle () { return this.email(); } }.initThisClass()); ``` That's the entire Contact class. The framework reads the slot annotations and generates: - An editable tile in the contacts list (showing `title()` and `subtitle()`) - An inspector view with labeled fields for each slot when you select it - Automatic persistence to IndexedDB whenever any field changes - Change notifications that keep the tile in sync with the field values ### Contacts A collection that holds Contact items: ```javascript (class Contacts extends SvJsonArrayNode { initPrototype () { this.setTitle("Contacts"); this.setSubnodeClasses([Contact]); this.setShouldStore(true); this.setShouldStoreSubnodes(true); this.setNodeCanAddSubnode(true); this.setNodeCanReorderSubnodes(true); } }.initThisClass()); ``` `setSubnodeClasses([Contact])` tells the framework what kind of objects this collection holds. `setNodeCanAddSubnode(true)` adds an "add" action to the UI. `setShouldStoreSubnodes(true)` persists the list and its contents. New contacts are created by tapping the add button — no handler code needed. ## The App ### ContactsModel The root model node. This is the top of the object hierarchy that becomes the navigation tree: ```javascript (class ContactsModel extends SvStorableNode { initPrototypeSlots () { { const slot = this.newSlot("contacts", null); slot.setFinalInitProto(Contacts); slot.setIsSubnode(true); } } initPrototype () { this.setShouldStore(true); this.setShouldStoreSubnodes(true); this.setTitle("Contacts App"); } }.initThisClass()); ``` `setFinalInitProto(Contacts)` tells the framework to create a `Contacts` instance during initialization — but only if one wasn't already loaded from storage. This is the key mechanism that makes the three-phase lifecycle work: on first run, the `Contacts` node is created fresh; on subsequent loads, it's restored from IndexedDB with all its data intact. `setIsSubnode(true)` places it in the navigation hierarchy. ### ContactsApp The application class — just declares which model and UI to use: ```javascript (class ContactsApp extends SvApp { initPrototypeSlots () { { const slot = this.overrideSlot("model"); slot.setSlotType("ContactsModel"); } } initPrototype () { this.setName("Contacts"); } }.initThisClass()); ``` That's the entire app class. `overrideSlot("model")` tells the base `SvApp` to use `ContactsModel` as the root. The framework handles everything else: opening the store, creating or loading the model, setting up the UI, and wiring synchronization. ## Project Files ### _imports.json The import manifest tells the build system which files to include: ```json [ "Contact.js", "Contacts.js", "ContactsModel.js", "ContactsApp.js" ] ``` A root-level `_imports.json` would reference the framework and this app folder: ```json [ "strvct/_imports.json", "app/_imports.json" ] ``` ### app.html The HTML entry point loads the boot loader: ```html Contacts ``` ### Build Run the indexer to generate the content-addressable resource bundle: ```bash node ./strvct/source/boot/index-builder/ImportsIndexer.js ``` This produces `_index.json` (metadata) and `_cam.json.zip` (compressed bundle). Serve the directory with any static file server and open `app.html`. ## What You Get From these four classes, the framework generates: - A navigable list of contacts with add and reorder support - Editable inspector fields for each contact's properties - Persistent storage — close the browser, reopen, data is still there - Bidirectional sync — edit a field, the tile title updates; changes batch efficiently - Keyboard navigation, breadcrumbs, and responsive column compaction - Slide-to-delete on contacts No view classes were written. No event handlers. No storage code. No routing. The domain model is the application. ## Adding Cloud Sync Assuming you have a Firebase project with Storage enabled, making the contacts collection sync to the cloud is a two-line change. Replace the base class and add a folder name: ```javascript (class Contacts extends SvSyncableArrayNode { initPrototype () { this.setTitle("Contacts"); this.setSubnodeClasses([Contact]); this.setShouldStore(true); this.setShouldStoreSubnodes(true); this.setNodeCanAddSubnode(true); this.setNodeCanReorderSubnodes(true); } cloudFolderName () { return "contacts"; } }.initThisClass()); ``` `SvSyncableArrayNode` extends `SvJsonArrayNode` with cloud sync capabilities. The `cloudFolderName()` method tells the framework where to store this collection's data in Firebase Storage. Everything else — delta tracking, manifest management, retry with backoff, and session locking — is inherited automatically. The collection syncs when the app saves, and loads from the cloud on the next login. --- Source: /FAQ/ # FAQ Frequently asked questions about STRVCT. ## What is the naked objects pattern? The idea that the domain model should be the user interface. Instead of writing separate view code for every screen, you annotate model classes with metadata (which properties are editable, which should persist, how they relate to each other) and the framework generates a complete, navigable UI from those annotations. The pattern was proposed by Richard Pawson in his 2004 PhD thesis. STRVCT's contribution is a set of composable UI primitives — tiles, tile stacks, and nested master-detail views — that make the generated interfaces feel like hand-crafted ones. ## Do I have to use the generated UI? No. The auto-generated views cover the common case, but you can override any node's view by creating a custom view class that follows the naming convention (e.g., `MyNodeView` for `MyNode`). In practice, most applications need very few custom views — undreamedof.ai has ~90 domain classes and fewer than 10 custom views, all for inherently graphical things like 3D dice, maps, and chat. ## Does it use npm or standard ES modules? No. The choice is load-bearing rather than stylistic. **Caching at scale:** For the kind of app STRVCT targets — hundreds of model classes running client-side, long-lived installs, data that stays resident across sessions — mainstream JavaScript tooling delivers a noticeably poor experience. Loading ES modules individually makes cold starts slow once you have a few hundred files. Bundlers solve the request count but invalidate the whole chunk on any change, and browser caches key on URL rather than content, so each deployment makes clients re-download bytes they already have. Code-splitting and cache-busting configurations narrow but don't close that gap. STRVCT uses a content-addressable build instead: resources are keyed by the SHA-256 hash of their content, so unchanged code is never re-downloaded — even across deployments, and even when the same code appears at different paths. You declare dependencies in `_imports.json` files, and the build tools produce a small manifest plus a hash-indexed bundle. The tradeoff is that standard import/export syntax isn't used within the framework, and npm packages can't be used directly. A handful of third-party libraries (pako, htmlparser2, jwt-decode, js-sha256, simple-peer) are included as source files in `external-libs/` rather than managed through a package manager. **Cross-platform:** Vendoring lets the framework ensure every dependency works in both the browser and Node.js, which matters because STRVCT's model layer is designed to run in both. Arbitrary npm packages frequently assume one environment or the other, which would conflict with that goal. **Security:** There are no transitive dependencies, no post-install scripts, and no exposure to supply chain attacks through compromised or malicious npm packages. The entire dependency tree is vendored source in the repository — auditable and version-controlled. ## Why not TypeScript? Integration cost. STRVCT's content-addressable build and in-browser source-level debugging (via eval with sourceURL) work cleanly with plain JavaScript. Adding a TS compile step while preserving content-hashed caching and step-through debugging looked like meaningful work for uncertain gain, especially given that slot annotations already carry type information at runtime and serve most of the same validation and documentation roles. The option remains open for the future, but it's not currently a priority. ## How does persistence work? Mark a class with `setShouldStore(true)` and its slots with `setShouldStoreSlot(true)`. The framework handles everything else: dirty tracking, batched commits at the end of each event loop, and transparent IndexedDB storage. Objects loaded from storage go through the same initialization lifecycle as new objects — no separate code paths. Cloud sync to Firebase Storage is available by extending `SvSyncableArrayNode` and providing a folder name. ## Is it local-first? Yes. Data lives in the browser's IndexedDB by default. The application works fully offline. Cloud sync is opt-in and additive — it backs up data and enables cross-device access, but the local database is always the primary store and serves as a cache for cloud data on subsequent loads. ## How is styling handled? All styling uses named JavaScript methods (`setBackgroundColor()`, `setFlexDirection()`, `setPadding()`) rather than CSS files. Themes are swappable dictionaries that can be scoped to any subtree. This avoids the combinatorial explosion of CSS selectors when views have multiple independent states (selected, disabled, highlighted, error, etc.) that interact visually. See the [Programmatic Styling](docs/Views/Programmatic%20Styling/index.html) docs for details. ## What about routing? There is no router. The object graph is the navigation structure. Nested objects produce drill-down columns. Breadcrumbs, column compaction on narrow viewports, and keyboard navigation are built in. Adding a new object type to the model automatically makes it navigable — no route configuration needed. ## What browsers are supported? Modern evergreen browsers (Chrome, Firefox, Safari, Edge). STRVCT uses contemporary JavaScript features like WeakRef, FinalizationRegistry, and private class fields. There is no transpilation step or polyfill layer. ## Can it run outside the browser? Yes. The model layer, persistence system, and boot sequence all run in Node.js with no DOM — the same application code, the same storage behavior, the same object lifecycle, just without the view layer. This is possible because the model never references views; views observe the model through notifications, not the other way around. Useful for tests, CLI tools, and server-side processing of the same object graphs the client uses. See [Headless Execution](../docs/Lifecycle/Headless%20Execution/index.html) for details. ## Is it production-ready? STRVCT is in active development and used in production by [undreamedof.ai](https://undreamedof.ai), an AI-powered virtual tabletop for D&D with ~90 domain classes, multiplayer sessions, cloud sync, and AI integration. The framework has been in continuous development since 2018. It is not yet widely adopted and the API may still change. ## What happens if the author stops working on this? MIT-licensed, and the entire codebase is readable: plain JavaScript, no transpiler, no opaque build chain, no transitive npm dependencies. The few third-party libraries are vendored source in `external-libs/`. If development stops, a sufficiently motivated user could fork it without needing to reverse-engineer a tooling stack first. It's not a reassuring answer in the sense of "you'll get corporate backing" — but compared to frameworks that lock you into their ecosystem, the escape route is short. ## What kinds of apps is it a good fit for? **App-shell SPAs:** logged-in, state-heavy products where the running application is the product and the marketing surface, if any, is a separate concern. A lot of the highest-valued consumer software being built right now fits this shape: OpenAI, Anthropic, xAI, Perplexity, Cursor (Anysphere), Harvey, Glean, Character.ai, ElevenLabs, Suno, Gamma, Midjourney — and, from the prior generation, Notion, Linear, Figma, Discord. Almost none of these depend on SSR or SEO for their core product. ChatGPT, Claude, Cursor, Figma, Linear, Notion, Discord, Character.ai, Suno, and Midjourney are all app-shell SPAs where the logged-in product is the product. They have marketing pages that need SEO, but those are typically a separate concern, often on a different stack entirely. The app itself is a client-side application with heavy state, real-time interaction, and no meaningful SEO surface. **Where it fits:** heavy client-side state, hundreds of domain classes, local-first persistence, first-class LLM integration, and a UI generated from the model rather than hand-assembled per screen. **Where it doesn't:** content platforms, traditional e-commerce, or marketing sites — categories where SEO and first-paint latency are competitive necessities and where frameworks like Next.js exist precisely to serve. The marketing surface around an app-shell SPA can be handled separately: [undreamedof.ai](https://undreamedof.ai) pairs a static marketing site with a STRVCT app on the same domain, which is usually the right split anyway. **Visual distinctiveness as the pitch:** the auto-generated UI has a consistent look of its own. Custom views can override any piece, but products where the interface is itself a design statement will spend more effort fighting the defaults than riding them. The framework suits products where correctness, data density, and navigation consistency matter more than visual differentiation. ## What's the history of this project? **Origins:** STRVCT began as internal infrastructure, not as a framework project. The earliest code was written in 2016 as part of an application called bitmarkets.js — Miller columns, a node hierarchy, and slot-based persistence — and was carried into a second unrelated application, voluntary.app, starting in 2017. The framework code was progressively separated from application code and extracted into its own repository, strvct.net, in 2020. It has since grown to include the object pool persistence system, gestures and drag-and-drop, content-addressable builds, cloud sync, and internationalization. The [Timeline](../Timeline/index.html) has the full list of milestones. **Public presence:** The project has not been publicly marketed or announced. Its only public presence is the GitHub repository and links from the author's GitHub and Twitter profile subtitles. It is not a commercial product, has no funding or sponsoring organization, and is developed as a personal framework — used in production by [undreamedof.ai](https://undreamedof.ai). ## How does it work with AI-assisted development? **Less to coordinate:** STRVCT's architecture is well suited to AI-assisted "vibe coding." In a conventional framework, building a feature means coordinating across components, stylesheets, state management, persistence wiring, route definitions, and API layers — an AI has to understand and keep all of these in sync. In STRVCT, a feature is typically a single model class with annotated slots. The AI only needs to describe the domain — what properties an object has, which should persist, which are editable — and the framework handles the rest. **What follows:** An AI can produce working, persistent, navigable applications from high-level descriptions of the domain model without needing to generate or coordinate view code, storage logic, or navigation. The consistent slot annotation pattern is easy to learn from examples, and the same pattern applies to every class in the system. For complex, data-model-driven applications, this can dramatically reduce the amount of code an AI needs to generate and the number of places things can go wrong. ## How do I get help when I'm stuck? The ecosystem is small. There's no Stack Overflow tag, no Discord, no book. What's there: - In-repo docs, regenerated from markdown and linked from the main index - [`llms.txt`](/llms.txt) and [`llms-full.txt`](/llms-full.txt), designed to be pasted into an LLM coding assistant - The framework source itself — vendored, readable, plain JavaScript with no transitive dependencies - [GitHub issues](https://github.com/stevedekorte/strvct.net/issues) for bug reports and questions In practice the recommended workflow is to give an LLM-backed coding agent (Claude Code, Cursor, Codex, etc.) the relevant docs and source as context, then let it reason from first principles. The slot/notification/naked-objects patterns are regular enough that a capable model can internalize them from a few dozen files — which is how the framework author works with it day-to-day. ## How do I get started? See the [Example App](Example%20App/index.html) for a complete working application in four classes, or the [Getting Started](docs/Getting%20Started/index.html) guide for setup and project structure. --- Source: /Timeline/ # Project Timeline Key milestones from the first commit through present day Framework architecture, UI system, persistence, and developer tooling. - **2016-01** — bitmarkets.js: First commit; Miller column browser, node hierarchy, and initial slot-based persistence - **2016-03** — NotificationCenter: Notification system added; node-to-view sync via deferred timeouts - **2016-04** — Framework Separation: Library code separated from app code; CSS split into framework and app layers - **2016-05** — StorableNode: BMStorableNode, IndexedDB persistence with dirty tracking and circular reference handling - **2017-02** — voluntary.app: Continued framework development under the voluntary.app repository - **2017-04** — IndexedDB Rewrite: NodeStore with IndexedDB via SyncDB; automatic dirty marking on slot updates - **2017-07** — Flex Columns: Browser columns moved to flex layout; touch scrolling and mobile orientation support - **2017-08** — Boot System: Boot folder and loading infrastructure; BMStorableNode separated from BMNode - **2018-06** — ES6 Classes: Migration from prototype-based Proto system to ES6 classes throughout the framework - **2019-02** — Gesture System: GestureRecognizer state machine with EventSetListener; TapGestureRecognizer - **2019-03** — Full Gesture Suite: LongPress, Slide, Pinch, and Rotation recognizers; GestureManager arbitration - **2019-09** — Drag and Drop: Drag-and-drop protocol with animations, placeholder views, and column caching - **2019-11** — Slot System: Formal Slot class with newSlot(); ObjectPool persistence; migration from Proto to classes - **2020-01** — strvct.net: New repository created; voluntary.app codebase imported as foundation - **2020-02** — Flex Layout: DomSplitView and flexible layout system for browser columns - **2020-02** — Image & Blob: Image wells, blob drops, MIME type support, and drag views - **2020-03** — Tile System: BrowserRows renamed to Tiles; tile-based navigation pattern solidified - **2020-04** — Content Store: Content-addressable blob storage with hash-based caching - **2020-05** — Themes: Theme system for customizable visual appearance - **2020-08** — WebRTC: PeerJS integration for real-time peer-to-peer communication - **2021-02** — Resource Loader: Rebuilt resource loading system with global compatibility fixes - **2022-01** — Boot Overhaul: Major cleanup of boot process, index building, and eval debugging - **2022-04** — CAM Build: Content-addressable memory build system with hash validation - **2022-05** — Breadcrumbs: BreadCrumbRowView for hierarchical navigation context - **2022-06** — Tile Rename: RowViews renamed to Tiles throughout the framework - **2022-08** — Object Pool: Major fixes for ObjectPool persistence, key handling, and event listeners - **2023-02** — App Bootstrap: Standardized App shared() pattern and resource setup - **2023-03** — Blob Cache: Content-addressable blob cache for efficient binary storage - **2023-07** — Blob Persistence: Full support for persisting blobs with reference tracking and GC - **2023-08** — WebRTC Service: WebRTC service layer for peer connection management - **2023-10** — HaveWords App: First major application built on STRVCT; later became Undreamed Of - **2024-01** — Music Library: Audio playback infrastructure for music and sound effects - **2024-04** — Error Handling: Error.rethrow() for chained error context; improved debugging - **2024-06** — Init Refactor: Split initPrototypeSlots and initPrototype for cleaner class setup - **2024-09** — Documentation: Comprehensive docs site with architecture guides and API reference - **2024-11** — Slot Maps: allSlotsMap fixes; improved slot introspection and enumeration - **2024-12** — AI Services: AI service abstraction layer supporting multiple providers - **2025-02** — Assistant API: Generalized AI Assistant and conversation management system - **2025-04** — Tool Calls: Structured tool calling system for AI function invocation - **2025-05** — Prompt Composer: Template-based prompt composition with file inclusion and variable substitution - **2025-09** — JSON Schema: Schema validation for model classes; AI-compatible type definitions - **2025-10** — Cloud Sync: Firebase Storage sync with write-ahead delta logs - **2025-11** — JSON Patches: RFC 6902 JSON Patch support for incremental model updates - **2026-01** — Image Gen: Image generation pipeline with evaluation and scaling - **2026-02** — Docs Site: Documentation site with layout engine, cards, and markdown support - **2026-03** — Internationalization: AI-powered UI translation with per-language IndexedDB and automatic model-to-view boundary translation - **2026-03** — Anchor Scroll: AI chat anchor scrolling and scroll-to-bottom button - **2026-03** — Battery Aware: Battery level monitoring for performance throttling detection - **2026-04** — AI Image Editor: Gemini-based image editing and scaling class hierarchy - **2026-04** — Accessibility: Automatic ARIA roles, labels, states, and landmarks derived from model annotations and view classes - **2026-04** — Public Cloud: SvPublicCloudSource for serving shared content via Firebase Storage manifests