Living Document Notice
Published 2026-09-11. The evolving architecture, revisions, and connected notes for this dispatch live in the Stax Digital Garden.

The Application Is Just a Lens

The Application Is Just a Lens: Stark monochrome P4 paper white vector CRT macro showing geometric optical lens refracting data vectors into multiple view projections

Summary

Software vendors routinely conflate the user interface with the underlying storage layer. When an application couples file formatting, metadata schemas, and index caches to its own binary runtime, users lose agency over their notes. If the application vendor ceases development or alters its subscription terms, access to the stored knowledge degrades or disappears.

The lens architectural model separates durable file structures from transient interface renderers. Under this design, the user interface functions strictly as an unprivileged, disposable viewport. Data persists on disk as plain text and standard relational tables, readable by external CLI utilities, scripts, or alternative editors at any time.

Storage Substrates Versus Ephemeral Viewports

In traditional productivity tools, the user interface claims exclusive ownership over user notes. The data lives inside closed formats, encrypted application containers, or remote proprietary databases. The FreeNext lens pattern inverts this dependency hierarchy.

+-------------------------------------------------------------+
|                     Filesystem Substrate                    |
|       (CommonMark Documents, Attachments, SQLite Index)      |
+-------------------------------------------------------------+
                               |
                               | POSIX I/O or OPFS Sync Handle
                               v
+-------------------------------------------------------------+
|                   Unprivileged VFS Adapter                  |
|          (Schema Parsing, Frontmatter Extraction)           |
+-------------------------------------------------------------+
                               |
                               | Read-Only Query Streams
                               v
+-------------------------------------------------------------+
|                      FreeNext Lens UI                       |
|           (Web Components, Canvas Visualizations)           |
+-------------------------------------------------------------+

The application client retains no private state stores. If a user deletes the client binary or clears browser caches, the knowledge archive remains intact on the host storage volume.

Architectural Trade-Offs: Monolithic Client vs. Unprivileged Lens

Decoupling storage from presentation introduces specific structural constraints. The table below details how the lens architecture contrasts with integrated application clients.

CharacteristicIntegrated Application ModelUnprivileged Lens Model
File AuthorityProprietary database formatFilesystem directory structure
Index LocationInaccessible internal cacheInspectable SQLite or JSON index
Write SemanticsSynchronous write to private storeDirect write to open disk target
Tool InteroperabilityRequires vendor export routinesAccessible via standard Unix CLI tools
Schema MigrationSilent, mandatory client migrationsVersioned, user-driven file transforms
Client Failure ImpactPotential total data lock-inClient replaced without data conversion

The primary engineering trade-off centers on query caching. Because external tools can alter files on disk without notifying the lens UI, the lens runtime must implement filesystem watch observers rather than assuming exclusive write control.

Implementing the Lens Read Interface

The lens communicates with local storage through an unprivileged Virtual Filesystem (VFS) boundary. The interface below illustrates how FreeNext executes read-only document streams without holding exclusive file locks:

export interface DocumentLensReader {
  scanDirectory(path: string): AsyncIterable<DocumentHeader>;
  readDocument(path: string): Promise<DocumentContent>;
  subscribeToChanges(path: string, callback: (event: FileChangeEvent) => void): Disposable;
}
 
export interface DocumentHeader {
  path: string;
  sha256: string;
  byteSize: number;
  lastModifiedEpoch: number;
  schemaVersion: number;
}
 
export interface DocumentContent {
  header: DocumentHeader;
  frontmatter: Record<string, unknown>;
  bodyCommonMark: string;
}
 
export class LocalVfsLens implements DocumentLensReader {
  private baseDirectoryHandle: FileSystemDirectoryHandle;
 
  constructor(directoryHandle: FileSystemDirectoryHandle) {
    this.baseDirectoryHandle = directoryHandle;
  }
 
  async *scanDirectory(subPath: string): AsyncIterable<DocumentHeader> {
    const dir = await this.resolvePath(subPath);
    for await (const [name, handle] of dir.entries()) {
      if (handle.kind === 'file' && name.endsWith('.md')) {
        const file = await (handle as FileSystemFileHandle).getFile();
        yield {
          path: `${subPath}/${name}`,
          sha256: await this.computeDigest(file),
          byteSize: file.size,
          lastModifiedEpoch: file.lastModified,
          schemaVersion: 1,
        };
      }
    }
  }
 
  async readDocument(path: string): Promise<DocumentContent> {
    const fileHandle = await this.resolveFile(path);
    const file = await fileHandle.getFile();
    const rawText = await file.text();
    return this.parseDocumentEnvelope(rawText, file);
  }
 
  private parseDocumentEnvelope(raw: string, file: File): DocumentContent {
    const separator = '---\n';
    if (!raw.startsWith(separator)) {
      return {
        header: { path: file.name, sha256: '', byteSize: file.size, lastModifiedEpoch: file.lastModified, schemaVersion: 1 },
        frontmatter: {},
        bodyCommonMark: raw,
      };
    }
    const end = raw.indexOf(separator, separator.length);
    const yamlChunk = raw.slice(separator.length, end);
    const body = raw.slice(end + separator.length);
    return {
      header: { path: file.name, sha256: '', byteSize: file.size, lastModifiedEpoch: file.lastModified, schemaVersion: 1 },
      frontmatter: this.parseYamlRecord(yamlChunk),
      bodyCommonMark: body,
    };
  }
 
  private parseYamlRecord(yaml: string): Record<string, unknown> {
    const result: Record<string, unknown> = {};
    for (const line of yaml.split('\n')) {
      const splitIdx = line.indexOf(':');
      if (splitIdx !== -1) {
        const key = line.slice(0, splitIdx).trim();
        const value = line.slice(splitIdx + 1).trim();
        result[key] = value.replace(/^["']|["']$/g, '');
      }
    }
    return result;
  }
 
  private async computeDigest(file: File): Promise<string> {
    const buffer = await file.slice(0, 4096).arrayBuffer();
    const hash = await crypto.subtle.digest('SHA-256', buffer);
    return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');
  }
 
  private async resolvePath(path: string): Promise<FileSystemDirectoryHandle> {
    return this.baseDirectoryHandle;
  }
 
  private async resolveFile(path: string): Promise<FileSystemFileHandle> {
    return await this.baseDirectoryHandle.getFileHandle(path);
  }
 
  subscribeToChanges(path: string, callback: (event: FileChangeEvent) => void): Disposable {
    return { dispose: () => {} };
  }
}

Treating the client interface as an unprivileged reader guarantees that data durability remains entirely independent of interface design trends.


  • Directus Target: freenext
  • Garden Source Reference: NXT-1000 - FreeNext Architecture Index, BSN-1001 - Bosun Ecosystem Architecture, DAT-1001 - Data Liberation Pipeline, MOC - Data Liberation Workbenches, MOC - The Plain-Text Longevity Standard, MOC - Local-First Systems and Synchronization, MOC - Bosun PKM Tools