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
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.
| Characteristic | Integrated Application Model | Unprivileged Lens Model |
|---|---|---|
| File Authority | Proprietary database format | Filesystem directory structure |
| Index Location | Inaccessible internal cache | Inspectable SQLite or JSON index |
| Write Semantics | Synchronous write to private store | Direct write to open disk target |
| Tool Interoperability | Requires vendor export routines | Accessible via standard Unix CLI tools |
| Schema Migration | Silent, mandatory client migrations | Versioned, user-driven file transforms |
| Client Failure Impact | Potential total data lock-in | Client 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