From a836be699262834f7f07921c990522d605219f38 Mon Sep 17 00:00:00 2001 From: vegard Date: Wed, 18 Mar 2026 15:15:08 +0000 Subject: [PATCH] Valider fase 11: fiks size-inkonsistens og UTF-8-trunkering i RSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fase 11 (produksjon) validert — LiveKit, pruning og podcast-RSS: - rss.rs + synops-rss: Les filstørrelse fra både 'size_bytes' (intentions) og 'size' (publishing) med COALESCE — forhindrer manglende enclosure- størrelse i podcast-feeds avhengig av opplastingsmetode. - pruning.rs + synops-prune: Samme COALESCE-fix for konsistent size-tracking. - rss.rs + synops-rss: Fiks truncate_description til å bruke char-indeksering istedenfor byte-indeksering — forhindrer panic på norsk tekst (å, ø, æ). LiveKit kjører i Docker (healthy), token-generering via join_communication, pruning-loop aktiv, RSS-endepunkt returnerer korrekt 404 for ukjent slug. Alle 61 maskinrommet-tester bestått. --- maskinrommet/src/pruning.rs | 2 +- maskinrommet/src/rss.rs | 15 +++++++++------ tasks.md | 3 +-- tools/synops-prune/src/main.rs | 2 +- tools/synops-rss/src/main.rs | 14 ++++++++------ 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/maskinrommet/src/pruning.rs b/maskinrommet/src/pruning.rs index 71e6705..c467fcf 100644 --- a/maskinrommet/src/pruning.rs +++ b/maskinrommet/src/pruning.rs @@ -222,7 +222,7 @@ async fn prune_by_ttl( WHEN n.metadata->>'mime' LIKE 'video/%' THEN 'video' ELSE 'other' END AS mime_category, - COALESCE((n.metadata->>'size_bytes')::bigint, 0) AS size_bytes, + COALESCE((n.metadata->>'size_bytes')::bigint, (n.metadata->>'size')::bigint, 0) AS size_bytes, n.created_at, n.last_accessed_at, EXISTS( diff --git a/maskinrommet/src/rss.rs b/maskinrommet/src/rss.rs index 96da5f8..516b0a2 100644 --- a/maskinrommet/src/rss.rs +++ b/maskinrommet/src/rss.rs @@ -193,7 +193,7 @@ async fn fetch_feed_items( e.metadata, m.metadata->>'cas_hash' AS cas_hash, m.metadata->>'mime' AS mime, - (m.metadata->>'size')::bigint AS size + COALESCE((m.metadata->>'size_bytes')::bigint, (m.metadata->>'size')::bigint) AS size FROM edges e JOIN nodes n ON n.id = e.source_id LEFT JOIN edges me ON me.source_id = n.id AND me.edge_type = 'has_media' @@ -476,13 +476,16 @@ fn short_id(id: Uuid) -> String { id.to_string()[..8].to_string() } -/// Trunkér beskrivelse til maks antall tegn, på ordgrense. -fn truncate_description(s: &str, max_len: usize) -> String { - if s.len() <= max_len { +/// Trunkér beskrivelse til maks antall tegn (chars, ikke bytes), på ordgrense. +fn truncate_description(s: &str, max_chars: usize) -> String { + let char_count = s.chars().count(); + if char_count <= max_chars { return s.to_string(); } - match s[..max_len].rfind(' ') { + // Finn byte-posisjon for max_chars tegn + let byte_end = s.char_indices().nth(max_chars).map(|(i, _)| i).unwrap_or(s.len()); + match s[..byte_end].rfind(' ') { Some(pos) => format!("{}…", &s[..pos]), - None => format!("{}…", &s[..max_len]), + None => format!("{}…", &s[..byte_end]), } } diff --git a/tasks.md b/tasks.md index e18c958..16360b2 100644 --- a/tasks.md +++ b/tasks.md @@ -299,8 +299,7 @@ med spesifikasjon for det som trenger en dedikert sesjon. - [x] 23.2 Valider fase 3–4 (frontend + tilgang): SvelteKit-oppsett, OIDC-flow, sanntid, mottaksflate, TipTap-editor, node_access-matrise, team-transitivitet, visibility-filtrering. - [x] 23.3 Valider fase 5–8 (kommunikasjon + CAS + lyd + aliaser): chat-loop, kontekst-arv, CAS-hashing/deduplisering, Whisper-pipeline, segmenttabell, SRT-eksport, alias-identitet. - [x] 23.4 Valider fase 9–10 (visninger + AI): kanban drag-and-drop, kalender, dagbok, kunnskapsgraf, LiteLLM-ruting, AI-foreslåtte edges, oppsummering, TTS. -- [~] 23.5 Valider fase 11 (produksjon): LiveKit-oppsett, sanntidslyd, pruning-logikk, podcast-RSS. - > Påbegynt: 2026-03-18T15:10 +- [x] 23.5 Valider fase 11 (produksjon): LiveKit-oppsett, sanntidslyd, pruning-logikk, podcast-RSS. - [ ] 23.6 Valider fase 13–14 (traits + publisering): trait-validering, pakkevelger, Tera-templates, HTML-rendering, forside, slot-håndtering, redaksjonell flyt, planlagt publisering, A/B-testing. - [ ] 23.7 Valider fase 15–16 (admin + lydmixer): systemvarsler, graceful shutdown, jobbkø-oversikt, ressursstyring, serverhelse, Web Audio mixer, delt kontroll, sound pads, EQ, stemmeeffekter. - [ ] 23.8 Valider fase 17–18 (lydstudio-utbedring + AI-verktøy): responsivt layout, FFmpeg-validering, fade/silence, AI-presets, direction-logikk, drag-and-drop integrasjon. diff --git a/tools/synops-prune/src/main.rs b/tools/synops-prune/src/main.rs index 704fbd5..99e9ef5 100644 --- a/tools/synops-prune/src/main.rs +++ b/tools/synops-prune/src/main.rs @@ -259,7 +259,7 @@ async fn phase_ttl( WHEN n.metadata->>'mime' LIKE 'video/%' THEN 'video' ELSE 'other' END AS mime_category, - COALESCE((n.metadata->>'size_bytes')::bigint, 0) AS size_bytes, + COALESCE((n.metadata->>'size_bytes')::bigint, (n.metadata->>'size')::bigint, 0) AS size_bytes, n.created_at, n.last_accessed_at, EXISTS( diff --git a/tools/synops-rss/src/main.rs b/tools/synops-rss/src/main.rs index 8062ba5..92afb88 100644 --- a/tools/synops-rss/src/main.rs +++ b/tools/synops-rss/src/main.rs @@ -268,7 +268,7 @@ async fn fetch_feed_items( e.metadata, m.metadata->>'cas_hash' AS cas_hash, m.metadata->>'mime' AS mime, - (m.metadata->>'size')::bigint AS size + COALESCE((m.metadata->>'size_bytes')::bigint, (m.metadata->>'size')::bigint) AS size FROM edges e JOIN nodes n ON n.id = e.source_id LEFT JOIN edges me ON me.source_id = n.id AND me.edge_type = 'has_media' @@ -562,13 +562,15 @@ fn short_id(id: Uuid) -> String { id.to_string()[..8].to_string() } -/// Trunkér beskrivelse til maks antall tegn, på ordgrense. -fn truncate_description(s: &str, max_len: usize) -> String { - if s.len() <= max_len { +/// Trunkér beskrivelse til maks antall tegn (chars, ikke bytes), på ordgrense. +fn truncate_description(s: &str, max_chars: usize) -> String { + let char_count = s.chars().count(); + if char_count <= max_chars { return s.to_string(); } - match s[..max_len].rfind(' ') { + let byte_end = s.char_indices().nth(max_chars).map(|(i, _)| i).unwrap_or(s.len()); + match s[..byte_end].rfind(' ') { Some(pos) => format!("{}…", &s[..pos]), - None => format!("{}…", &s[..max_len]), + None => format!("{}…", &s[..byte_end]), } }