fix: several divergences of code from spec tackled

This commit is contained in:
2026-04-05 08:35:43 +02:00
parent f72d87eafe
commit 75cb19a604
15 changed files with 422 additions and 51 deletions

View File

@@ -85,7 +85,7 @@ rule ImportMedia {
width: detect_width(source_file),
height: detect_height(source_file),
file_path: dest,
tags: []
tags: {}
)
ensures: FileCopied(source_file, dest)
ensures: SidecarWritten(media)

View File

@@ -41,7 +41,7 @@ invariant ThumbnailPathBucketing {
-- Thumbnails are bucketed by first 2 chars of media ID
-- This avoids filesystem slowdowns from too many files in one directory
for m in Media:
let prefix = m.id[0:2]
let prefix = substring(m.id, 0, 2)
m.thumbnails.small = format("thumbnails/{prefix}/{id}-small.webp",
prefix: prefix, id: m.id)
m.thumbnails.medium = format("thumbnails/{prefix}/{id}-medium.webp",
@@ -62,8 +62,7 @@ config {
thumbnail_medium_width: Integer = 400
thumbnail_large_width: Integer = 800
thumbnail_ai_size: Integer = 448 -- 448x448 square crop, JPEG
thumbnail_quality: Integer = 80 -- WebP quality, hardcoded
thumbnail_format: String = "webp" -- All sizes except AI
thumbnail_format: String = "webp" -- All sizes except AI (encoder default quality)
thumbnail_ai_format: String = "jpeg" -- AI thumbnail only
}
@@ -75,21 +74,18 @@ rule GenerateThumbnails {
source: media.file_path,
destination: media.thumbnails.small,
width: config.thumbnail_small_width,
quality: config.thumbnail_quality,
format: config.thumbnail_format
)
ensures: ThumbnailGenerated(
source: media.file_path,
destination: media.thumbnails.medium,
width: config.thumbnail_medium_width,
quality: config.thumbnail_quality,
format: config.thumbnail_format
)
ensures: ThumbnailGenerated(
source: media.file_path,
destination: media.thumbnails.large,
width: config.thumbnail_large_width,
quality: config.thumbnail_quality,
format: config.thumbnail_format
)
ensures: ThumbnailGenerated(
@@ -103,10 +99,10 @@ rule GenerateThumbnails {
-- Thumbnail generation algorithm
value ThumbnailGeneration {
-- 1. Load source image
-- 2. Read raw image dimensions from header (not EXIF-orientation-aware)
-- 2. Apply EXIF orientation correction (rotation, flip) so thumbnails display correctly
-- 3. Resize: small/medium/large preserve aspect ratio (width-constrained)
-- AI thumbnail is a 448x448 center crop
-- 4. Encode as WebP (quality 80) for small/medium/large
-- AI thumbnail is a 448x448 center crop (letterboxed on black background)
-- 4. Encode as WebP (encoder default quality) for small/medium/large
-- Encode as JPEG for AI thumbnail
-- 5. Write to bucketed thumbnail path: thumbnails/{id[0:2]}/{id}-{size}.{ext}
--
@@ -114,12 +110,9 @@ value ThumbnailGeneration {
}
invariant ThumbnailExifHandling {
-- TypeScript app reads raw image header dimensions
-- It does NOT apply EXIF orientation correction during thumbnail generation
-- Width/height stored in DB are the raw header values
@guidance
-- The Rust implementation should match this: raw header dimensions
-- EXIF auto-rotation can be added later as an enhancement
-- EXIF orientation IS applied during thumbnail generation so that
-- thumbnails always appear right-side-up regardless of camera metadata.
-- Width/height stored in DB are the raw header values (pre-rotation).
}
-- ============================================================================
@@ -141,7 +134,7 @@ value ImageProcessing {
}
-- Processing rules:
-- 1. All thumbnails (except AI) are encoded as WebP quality 80
-- 1. All thumbnails (except AI) are encoded as WebP (encoder default quality)
-- 2. AI thumbnail is encoded as JPEG (for vision model compatibility)
-- 3. Original format is preserved for full-size assets (no conversion)
-- 4. EXIF data is not stripped (thumbnails are re-encoded, so EXIF is naturally absent)

View File

@@ -112,8 +112,8 @@ rule CreatePost {
status: draft,
author: author,
language: language,
tags: tags ?? [],
categories: categories ?? [],
tags: tags ?? {},
categories: categories ?? {},
template_slug: template_slug,
do_not_translate: false,
file_path: ""

View File

@@ -381,21 +381,23 @@ invariant UniqueDismissedDuplicatePair {
value Fts5PostSchema {
-- CREATE VIRTUAL TABLE posts_fts USING fts5(
-- title, excerpt, content, tags, categories,
-- content='posts',
-- content_rowid='rowid'
-- post_id UNINDEXED,
-- title, excerpt, content, tags, categories
-- );
fields: Set<String> -- {title, excerpt, content, tags, categories}
-- Standalone table (no content-sync) because text is pre-stemmed
-- via Snowball before insertion; content-sync would read un-stemmed
-- base-table text at query time instead.
fields: Set<String> -- {post_id UNINDEXED, title, excerpt, content, tags, categories}
stemmer_languages: Integer = 24
}
value Fts5MediaSchema {
-- CREATE VIRTUAL TABLE media_fts USING fts5(
-- title, alt, caption, original_name, tags,
-- content='media',
-- content_rowid='rowid'
-- media_id UNINDEXED,
-- title, alt, caption, original_name, tags
-- );
fields: Set<String> -- {title, alt, caption, original_name, tags}
-- Standalone table (no content-sync) — same rationale as posts_fts.
fields: Set<String> -- {media_id UNINDEXED, title, alt, caption, original_name, tags}
stemmer_languages: Integer = 24
}