feat(zitare): integrate spiral-db for visual quote storage

Add spiral-db integration to Zitare as the second app (after Todo) to
use pixel-based spiral data visualization. Favorites are encoded as
colored pixels in a spiral pattern and can be exported/imported as PNG.

Changes:
- Add createQuoteSchema() to spiral-db with fields for category,
  language, author, text, and quoteId
- Create Svelte 5 spiral store with importFavorites, CRUD, PNG export
- Add SpiralCanvas component for interactive visualization
- Add /spiral route with stats, records list, and actions
- Wire up navigation (Ctrl+6) and auto-import favorites on mount

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-23 10:44:39 +01:00
parent 67a181bb04
commit 5bcbb4b71d
8 changed files with 1018 additions and 1 deletions

View file

@ -92,6 +92,7 @@ export {
// Schema utilities
export {
createTodoSchema,
createQuoteSchema,
encodeSchema,
decodeSchema,
getSchemaPixelCount,

View file

@ -8,6 +8,7 @@ import {
decodeSchema,
getSchemaPixelCount,
createTodoSchema,
createQuoteSchema,
validateRecord,
getFieldNames,
} from './schema.js';
@ -193,6 +194,52 @@ describe('validateRecord', () => {
});
});
describe('Quote Schema', () => {
it('should create quote schema with correct fields', () => {
const schema = createQuoteSchema();
expect(schema.name).toBe('quote');
expect(schema.version).toBe(1);
expect(schema.fields).toHaveLength(8);
expect(schema.fields.map((f) => f.name)).toEqual([
'id',
'status',
'category',
'language',
'createdAt',
'quoteId',
'author',
'text',
]);
});
it('should round-trip quote schema encode/decode', () => {
const schema = createQuoteSchema();
const pixels = encodeSchema(schema);
const names = getFieldNames(schema);
const decoded = decodeSchema(pixels, names);
expect(decoded.fields.length).toBe(schema.fields.length);
for (let i = 0; i < schema.fields.length; i++) {
expect(decoded.fields[i].type).toBe(schema.fields[i].type);
expect(decoded.fields[i].maxLength).toBe(schema.fields[i].maxLength);
}
});
it('should validate a valid quote record', () => {
const schema = createQuoteSchema();
const result = validateRecord(schema, {
id: 0,
status: 0,
category: 3,
language: 1,
createdAt: new Date(),
quoteId: 'q-123',
author: 'Goethe',
text: 'Ein kluges Wort',
});
expect(result.valid).toBe(true);
});
});
describe('getFieldNames', () => {
it('should return field names in order', () => {
const schema = createTodoSchema();

View file

@ -110,6 +110,26 @@ export function createTodoSchema(): SchemaDefinition {
};
}
/**
* Create a schema for Quote items (Zitare app)
*/
export function createQuoteSchema(): SchemaDefinition {
return {
version: 1,
name: 'quote',
fields: [
{ name: 'id', type: 'int', maxLength: 12 }, // 0-4095
{ name: 'status', type: 'int', maxLength: 3 }, // 0=active, 2=favorited, 4=removed
{ name: 'category', type: 'int', maxLength: 4 }, // 10 categories (0-15)
{ name: 'language', type: 'int', maxLength: 3 }, // 6 languages (0-7)
{ name: 'createdAt', type: 'timestamp', maxLength: 24 },
{ name: 'quoteId', type: 'string', maxLength: 100 }, // Reference to content package
{ name: 'author', type: 'string', maxLength: 100 },
{ name: 'text', type: 'string', maxLength: 255 },
],
};
}
/**
* Validate that a record matches a schema
*/