test(examples): add failing behavioral tests before app implementation
Actual project-only Git commit. Email addresses are redacted; source bytes and recorded timestamps are preserved. This is a code record, not human acceptance.
commit 2aec720e3ab5d92f84867b1a371c988c711807b8
Author: 0xLeif <redacted@invalid>
AuthorDate: Sun Nov 8 19:26:05 2026 -0700
Commit: 0xLeif <redacted@invalid>
CommitDate: Sun Nov 8 19:26:05 2026 -0700
test(examples): add failing behavioral tests before app implementation
diff --git a/apps/pocket-notes/package.json b/apps/pocket-notes/package.json
new file mode 100644
index 0000000..8e80c1e
--- /dev/null
+++ b/apps/pocket-notes/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@corvid-examples/pocket-notes",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "license": "MIT",
+ "scripts": {
+ "test": "bun test",
+ "typecheck": "tsc --project tsconfig.json",
+ "build": "bun run build.ts",
+ "start": "bun run serve.ts"
+ },
+ "devDependencies": {
+ "typescript": "5.9.3"
+ }
+}
diff --git a/apps/pocket-notes/src/model.ts b/apps/pocket-notes/src/model.ts
new file mode 100644
index 0000000..b3059c2
--- /dev/null
+++ b/apps/pocket-notes/src/model.ts
@@ -0,0 +1,22 @@
+export type ModelResult<Value> =
+ | { readonly ok: true; readonly value: Value }
+ | { readonly ok: false; readonly error: string };
+
+export interface FileExport {
+ readonly filename: string;
+ readonly mediaType: "application/json" | "text/plain";
+ readonly contents: string;
+}
+
+export interface NoteState {
+ readonly version: 1;
+ readonly text: string;
+ readonly updatedAt: number;
+}
+export const MAX_NOTE_LENGTH = 20_000;
+export function createNoteState(): NoteState { throw new Error("Not implemented"); }
+export function validateNoteState(_value: unknown): ModelResult<NoteState> { throw new Error("Not implemented"); }
+export function editNote(_state: NoteState, _text: string, _at: number): ModelResult<NoteState> {
+ throw new Error("Not implemented");
+}
+export function createNoteExport(_state: NoteState): FileExport { throw new Error("Not implemented"); }
diff --git a/apps/pocket-notes/src/storage.ts b/apps/pocket-notes/src/storage.ts
new file mode 100644
index 0000000..78d8b25
--- /dev/null
+++ b/apps/pocket-notes/src/storage.ts
@@ -0,0 +1,21 @@
+import type { NoteState } from "./model";
+
+export interface StorageAdapter {
+ getItem(key: string): string | null;
+ setItem(key: string, value: string): void;
+}
+export type LoadResult<Value> =
+ | { readonly status: "loaded"; readonly value: Value }
+ | { readonly status: "empty" }
+ | { readonly status: "warning"; readonly message: string };
+export type SaveResult =
+ | { readonly ok: true }
+ | { readonly ok: false; readonly message: string };
+
+export const NOTE_STORAGE_KEY = "corvid-examples:pocket-notes:v1";
+export function loadNote(_storage: StorageAdapter): LoadResult<NoteState> {
+ throw new Error("Not implemented");
+}
+export function saveNote(_storage: StorageAdapter, _state: NoteState): SaveResult {
+ throw new Error("Not implemented");
+}
diff --git a/apps/pocket-notes/tests/model.test.ts b/apps/pocket-notes/tests/model.test.ts
new file mode 100644
index 0000000..72bf76a
--- /dev/null
+++ b/apps/pocket-notes/tests/model.test.ts
@@ -0,0 +1,70 @@
+import { expect, test } from "bun:test";
+import { createNoteState, createNoteExport, editNote, validateNoteState,
+ type ModelResult, type NoteState } from "../src/model";
+
+const initial: NoteState = { version: 1, text: "Earlier", updatedAt: 10 };
+function value<Value>(result: ModelResult<Value>): Value {
+ if (!result.ok) throw new Error(result.error);
+ return result.value;
+}
+test("creates one empty versioned note", () => {
+ expect(createNoteState()).toEqual({ version: 1, text: "", updatedAt: 0 });
+});
+test("editing preserves exact multiline Unicode and leaves previous state unchanged", () => {
+ const before = structuredClone(initial);
+ const text = " Café 🐦\nSecond line\n ";
+ expect(value(editNote(initial, text, 20))).toEqual({ version: 1, text, updatedAt: 20 });
+ expect(initial).toEqual(before);
+});
+test("second edit and explicit clearing are valid", () => {
+ const edited = value(editNote(initial, "Second", 20));
+ expect(value(editNote(edited, "", 21))).toEqual({ version: 1, text: "", updatedAt: 21 });
+ expect(edited.text).toBe("Second");
+});
+test("accepts 20000 characters and equal timestamps", () => {
+ expect(value(editNote(initial, "x".repeat(20000), 10)).text).toHaveLength(20000);
+});
+test("rejects oversized text without mutating prior note", () => {
+ const before = structuredClone(initial);
+ {
+ const previous = structuredClone(initial);
+ const rejected = editNote(initial, "x".repeat(20001), 20);
+ expect(rejected.ok).toBe(false);
+ if (!rejected.ok) expect(rejected.error.length).toBeGreaterThan(0);
+ expect(initial).toEqual(previous);
+ }
+ expect(initial).toEqual(before);
+});
+for (const at of [-1, 9, NaN, Infinity, 1.5, Number.MAX_SAFE_INTEGER + 1]) {
+ test("rejects invalid or decreasing edit time: " + at, () => {
+ {
+ const previous = structuredClone(initial);
+ const rejected = editNote(initial, "Changed", at);
+ expect(rejected.ok).toBe(false);
+ if (!rejected.ok) expect(rejected.error.length).toBeGreaterThan(0);
+ expect(initial).toEqual(previous);
+ }
+ });
+}
+test("validates a nonempty note into an independent value", () => {
+ const fixture = { version: 1, text: "Saved\nexactly ", updatedAt: 20 };
+ const result = value(validateNoteState(fixture));
+ fixture.text = "Changed outside";
+ expect(result.text).toBe("Saved\nexactly ");
+});
+const invalid: readonly unknown[] = [
+ null, [], {}, { ...initial, version: 2 }, { ...initial, text: 42 },
+ { ...initial, text: "x".repeat(20001) }, { ...initial, updatedAt: NaN },
+ { ...initial, updatedAt: -1 }, { ...initial, updatedAt: 1.5 }, { ...initial, extra: true },
+];
+invalid.forEach((input, index) => test("rejects malformed persisted note " + index, () => {
+ const result = validateNoteState(input);
+ expect(result.ok).toBe(false);
+ if (!result.ok) expect(result.error.length).toBeGreaterThan(0);
+}));
+test("exports exact current note text without headers or appended newline", () => {
+ const note: NoteState = { version: 1, text: " Café 🐦\nKeep whitespace ", updatedAt: 20 };
+ expect(createNoteExport(note)).toEqual({
+ filename: "pocket-note.txt", mediaType: "text/plain", contents: note.text,
+ });
+});
diff --git a/apps/pocket-notes/tests/storage.test.ts b/apps/pocket-notes/tests/storage.test.ts
new file mode 100644
index 0000000..d1ae640
--- /dev/null
+++ b/apps/pocket-notes/tests/storage.test.ts
@@ -0,0 +1,76 @@
+import { expect, test } from "bun:test";
+import { editNote, type NoteState } from "../src/model";
+import { loadNote, saveNote, NOTE_STORAGE_KEY, type StorageAdapter } from "../src/storage";
+
+const fixture: NoteState = { version: 1, text: "Saved\nexactly ", updatedAt: 20 };
+const expectedKey = "corvid-examples:pocket-notes:v1";
+
+test("loads absent storage without writes or destructive reset", () => {
+ const reads: string[] = [];
+ let writes = 0;
+ const result = loadNote({ getItem(key) { reads.push(key); return null; }, setItem() { writes++; } });
+ expect(result).toEqual({ status: "empty" });
+ expect(reads).toEqual([expectedKey]);
+ expect(writes).toBe(0);
+});
+test("loads actual nonempty JSON through model validation", () => {
+ const result = loadNote({ getItem(key) { expect(key).toBe(expectedKey); return JSON.stringify(fixture); }, setItem() { throw new Error("Unexpected write"); } });
+ expect(result).toEqual({ status: "loaded", value: fixture });
+});
+for (const raw of ["{", JSON.stringify({ version: 9 }), "null", "[]"]) {
+ test("warns on malformed or unsupported persisted data: " + raw, () => {
+ let writes = 0;
+ const result = loadNote({ getItem() { return raw; }, setItem() { writes++; } });
+ expect(result.status).toBe("warning");
+ if (result.status === "warning") expect(result.message.length).toBeGreaterThan(0);
+ expect("value" in result).toBe(false);
+ expect(writes).toBe(0);
+ });
+}
+test("warns when the actual adapter read throws", () => {
+ const result = loadNote({ getItem() { throw new Error("Storage denied"); }, setItem() { throw new Error("Unexpected write"); } });
+ expect(result.status).toBe("warning");
+ if (result.status === "warning") expect(result.message.length).toBeGreaterThan(0);
+ expect("value" in result).toBe(false);
+});
+test("writes correct key and round-trips exact state through real helpers", () => {
+ const map = new Map<string, string>();
+ const writes: [string, string][] = [];
+ const adapter: StorageAdapter = {
+ getItem(key) { return map.get(key) ?? null; },
+ setItem(key, contents) { writes.push([key, contents]); map.set(key, contents); },
+ };
+ expect(saveNote(adapter, fixture)).toEqual({ ok: true });
+ expect(writes).toHaveLength(1);
+ expect(writes[0]?.[0]).toBe(expectedKey);
+ expect(map.get(NOTE_STORAGE_KEY)).toBe(JSON.stringify(fixture));
+ expect(loadNote(adapter)).toEqual({ status: "loaded", value: fixture });
+});
+test("actual write failure preserves current state and returns useful warning", () => {
+ const before = structuredClone(fixture);
+ let calls = 0;
+ const result = saveNote({ getItem() { return null; }, setItem(key) { expect(key).toBe(expectedKey); calls++; throw new Error("Quota exceeded"); } }, fixture);
+ expect(result.ok).toBe(false);
+ if (!result.ok) expect(result.message.length).toBeGreaterThan(0);
+ expect(calls).toBe(1);
+ expect(fixture).toEqual(before);
+});
+test("save validates state before touching adapter", () => {
+ let calls = 0;
+ const invalid = { ...fixture, version: 2 } as unknown as NoteState;
+ const result = saveNote({ getItem() { return null; }, setItem() { calls++; } }, invalid);
+ expect(result.ok).toBe(false);
+ expect(calls).toBe(0);
+});
+test("current real model edit survives failure then saves and reloads on recovery", () => {
+ const edited = editNote({ version: 1, text: "Earlier", updatedAt: 10 }, "Saved\nexactly ", 20);
+ if (!edited.ok) throw new Error(edited.error);
+ const before = structuredClone(edited.value);
+ const failed = saveNote({ getItem() { return null; }, setItem() { throw new Error("Unavailable"); } }, edited.value);
+ expect(failed.ok).toBe(false);
+ expect(edited.value).toEqual(before);
+ let saved: string | null = null;
+ const adapter: StorageAdapter = { getItem() { return saved; }, setItem(_key, contents) { saved = contents; } };
+ expect(saveNote(adapter, edited.value)).toEqual({ ok: true });
+ expect(loadNote(adapter)).toEqual({ status: "loaded", value: before });
+});
diff --git a/apps/pocket-notes/tsconfig.json b/apps/pocket-notes/tsconfig.json
new file mode 100644
index 0000000..5c09ca1
--- /dev/null
+++ b/apps/pocket-notes/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "lib": [
+ "ES2022",
+ "DOM",
+ "DOM.Iterable"
+ ],
+ "strict": true,
+ "noEmit": true,
+ "skipLibCheck": true,
+ "types": []
+ },
+ "include": [
+ "src/**/*.ts"
+ ]
+}