fix: several divergences of code from spec tackled
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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: ""
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user