Versioning · Keep a Changelog
Everything that changed.
Transparent change history of TubeLens, from the first public version to today. Each entry lists what was added, changed, fixed or migrated.
Current version v0.3.0
Added
- TLR · TubeLens Editorial Rating — three-axis classification system. Every analysis now ships with the Lupometer (editorial quality in 4 states: Certified → Clear → Neutral → Red), the Suggested Age tier (5 levels — 0+ / 10+ / 13+ / 16+ / 18+ — derived from 12 content signals), and the Editorial Seal (Full · Partial · None, inspired by FCC §73.1212 and FTC Endorsement Guides). Full spec in
docs/tlr-methodology.md. Inspired by public IARC principles, without affiliation or endorsement. - Self-critique pass in the analysis pipeline — a second LLM call verifies conformity with the calibration traps and emits structured corrections that are applied deterministically in code.
- 5 TLR SEO pages:
/safe-for-kids,/teen-safe,/adult-only,/editorial-pleno,/certified-videos. - Inline TLR filters in
/rankings/videosand/rankings/channels(age tier · editorial seal · lupometer). Plain GET form preserving existing filters. - TlrViewsNav — 5 chips at the top of
/rankings,/rankings/videos,/rankings/channels,/channels, and/category/[c]. - "Copy analysis" button producing a formatted text ready to paste as a YouTube comment. Chrome extension (v0.4.0) has the equivalent.
- JSON-LD
audience.suggestedMinAge+contentRatingon the Review'sitemReviewed(VideoObject) — recognised by Google and Bing as age suitability. - Channel-level TLR aggregation: 5 new columns on
channels(dominant_age_tier,dominant_editorial_seal,dominant_lupometer_state,tlr_certified_count,tlr_clear_or_better_pct) with an automatic trigger onvideo_analysesand full backfill. - Admin panel at
/admin/tlrwith distributions across the three axes and frequency of the 12 suitability signals. - INOSX institutional band in the global footer (
support@inosx.com·+1 (302) 457-7323· "by INOSX" → inosx.com). - Floating version badge at the bottom-right of every page, with a popover summarising the latest releases and a link to a public
/changelogpage.
Changed
- AI provider migrated from OpenRouter free models (
nemotron,qwen3-next) to OpenAI directly (gpt-4.1-minivia@ai-sdk/openai). Free models hit Vercel function timeouts with the expanded TLR prompt; latency dropped to ~3–5s per analysis. OpenRouter is kept as an emergency fallback. - Calibration softening pass (2026-05-14) — after detecting excessive compression (80% of analyses falling into
weak), trap ceilings were softened by +1 point on credibility/density/originality, the "default to lower when unsure" trap was rewritten as "score honestly across the full range", and the critic now applies a tolerance of 1.0 (sub-point deltas are treated as noise rather than inflation). - Lupometer
lup_blurredrenamed from "Lupa Embaçada / Blurred Lens / Lupa Empañada" to "Lupa Vermelha / Red Lens / Lupa Roja". The previous label read as a defect of the instrument (the magnifying glass), not the content. - Verdict UI reinforced on
/analyze/[videoId]: the score number and the seal are now rendered in a gradient color-coded by score, the mascot grew from 96 to 112px, and the LupometerChip is shown below the seal. - LLM schema: the
suitability_signalsarray is now mandatory and always emits all 12 signals with intensity 0–5. Schema is compatible with OpenAI structured outputs (no.optional()on required fields). - Public pages updated with the TLR narrative:
/,/about,/pricing,/founders,/methodology,/pro,/extension,/sources,/press. Pricing tier features include TLR access (Reader: full classification; Editor: advanced filters). - Chrome extension bumped from 0.3.0 → 0.4.0 with full TLR in the popup and an updated share template.
Migrations applied
tlr_v0_1_columns·rank_videos_tlr·channel_tlr_aggregates·recompute_channel_tlr_stats·rank_channels_tlr
Changed
- Visual palette repositioned — investigative/Bellingcat-inspired vibe (initially staged for validation): - Background deep navy (#0a1929) replaces pure near-black. - Foreground warm cream (#f4ead5) replaces pure white — editorial vibe. - Primary accent copper (#cc7849) with gradient end copper-bright (#e88f4a) replaces violet/fuchsia. - Gold (#d4a574) as secondary accent on clip-text headings and highlighted text. - Red/amber/green preserve their functional semantics (Avoid/Acceptable/Recommended). - Logo, mascot (web + extension), and 42+ hardcoded occurrences of
violet-*/fuchsia-*migrated to CSS vars (var(--accent),var(--accent-2),var(--gold),var(--accent-deep)). - Chrome extension bumped 0.2.2 → 0.3.0 (public zip regenerated). - Strategic rationale: YouTube's black + red would conflict with the "popularity ≠ quality" positioning and risk trademark dilution. The investigative palette (navy + copper + cream) reinforces the editorial / research-grade vibe aligned with the thesis.
Added
- Public category pages at
/[locale]/category/[category]— 11 categories × 3 locales = 33 SEO-friendly indexable URLs. Each shows top 10 best and worst channels + top 10 best and worst videos for the category, with 4ItemListJSON-LD blocks (strong signal for Google and AI crawlers), search-optimised heading ("Best and worst [category] channels on YouTube"), cross-navigation to other categories, and a link to the full ranking. Added to the sitemap. "Categories" link in the footer (discovery). Works as an acquisition funnel: anonymous visitor discovers via Google → sees the public ranking → creates an account to run an analysis. - Formatted duration and "Short" badge shown across 3 surfaces to close the enriched metadata loop: -
/analyze/[videoId]: duration + view count + Short badge in the header (next to the channel name). -/channel/[handle]: duration + Short badge on each video card in the grid. -/history: duration + Short badge on each history card. - HelpersformatDuration()("1h12m" / "5m32s" / "42s") andformatViews()(locale-compact) inlib/format.ts. - Engagement metadata via Innertube (free, no API key).
fetchEnrichedMetadata()providesview_count,like_count,duration_sec,published_at,is_short(inferred from duration ≤60s) at no extra cost. The schema gained 5 new columns onvideo_analyses. <DivergenceCallout>component on/analyze/[videoId]— visible editorial differential per analysis. Renders when there's a divergence between views and score: - Viral but bad (≥500K views + score ≤4.5) → red callout with ⚠ icon. - Hidden gem (≤10K views + score ≥7.5) → green/violet callout with 💎 icon. - Silent when there's no relevant divergence. Metrics never enter the score — this is a post-hoc reading of the relation between the two variables. pt/en/es.- Appeals page in the methodology. New
/methodology#appealssection documents the public process for a channel owner to contest a seal, score, detected signal, or justification. Covers: who can appeal, what can be appealed (5 items), how to appeal (4 items), turnaround (5 business days) + 4 possible outcomes (re-analysis, annotation, removal, justified retention), and 4 principles (transparency, non-retaliation, good faith, AI-separated process). Added to ToC + FAQ JSON-LD. Good-faith defense against defamation + fair process. pt/en/es. - Transcript clause in the privacy policy. Explicitly documents: the full transcript is not stored permanently (24h cache), not publicly displayed (only snippets cited in justifications — fair use), and the procedure for a channel owner to request removal of a cited excerpt. pt/en/es.
- Footer with links (Methodology, Privacy, Extension, Pro, Contact) + disclaimer "TubeLens is independent, not affiliated with YouTube/Google" + copyright. Meets YouTube brand guidelines and mitigates trademark dilution risk. Applied in pt/en/es.
- Video embed via
youtube-nocookie.comon/analyze/[videoId]instead of a static thumbnail. The user can validate the video before/during reading the analysis. Privacy-enhanced mode (zero YouTube cookies until clicking play). Lazy-loaded. - Enriched JSON-LD (
Review.positiveNotes/negativeNotes): detected green and red labels becomeItemLists in the schema. Unlocks rich results for critical reviews and improves citation by LLMs (ChatGPT, Claude, Perplexity, Gemini). VideoObject.embedUrlpointing toyoutube-nocookie.com/embed/{videoId}in the analyse page's schema.org.- Magnifying-glass mascot (static 36px) in the seal table of the methodology. Each score band has its own expression (exceptional → stars, recommended → arc-up, acceptable → neutral, weak → eyebrow, avoid → tongue-out). Reinforces the visual identity and helps quick scanning of the scale.
- Magnifying-glass mascots per seal (5 expressions: exceptional/recommended/acceptable/weak/avoid) replacing the play-triangle inside the lens — preserves the brand silhouette. Animated on
/analyze/[videoId],/rankings,/rankings/{channels,videos}, and/channel/[handle]; static on/channels,/history,ChannelCard. Respectsprefers-reduced-motion. - Chrome extension v0.2.2: animated mascot inside the result card. Vanilla-JS port of
ScoreMascot.tsxinextension/lib/mascot.js. Same silhouette, same timing, same color system. staging.tubelens.appdomain attached to thedevelopbranch with its own Preview env vars (NEXT_PUBLIC_SITE_URLpoints to staging).- Branded Supabase email templates managed via
scripts/update-email-templates.mjs(Management API). - INOSX multi-project standard: versioned
CLAUDE.md,.claude/settings.jsonwith aPreToolUse(Bash)hook blocking personal email in commits, slash commands (/preflight,/changelog-add,/sync-develop,/promote-prod,/email-audit,/tubelens-credits,/tubelens-redeploy). develop↔mainbranch flow formalised with a guided promotion process.
Changed
- Shorts excluded from rankings and aggregated averages. YouTube Shorts (videos ≤60s) don't fit the 4-dimension rubric (density / clarity / credibility / originality). Filter applied to: - RPC
rank_channels(Bayesian channel ranking) - RPCrank_videos(video ranking) - RPCrecompute_channel_stats(channel aggregates) - JS querycomputeChannelMovement(↑↓ movement across periods) - Legacy rows withis_short=NULLpass through (we can't infer retroactively — we assume they weren't Shorts). - Individual Short analyses remain accessible via/analyze/[videoId]— only the rankings exclude them. - Documented inmethodologySection 6 with a 3rd rules card (pt/en/es). - Score calibration — wave 2 (affects new analyses only): - After wave 1 (commit 5e6cc27): average rose 5.97 → 6.31 (+0.34) and the "Avoid" seal dropped from 15% to 6%, but the ceiling stayed at 8.2 with zero "Exceptional" across 17 analyses. - Wave 2 adds an explicit top-end reinforcement to the prompt with a multi-dimensional criterion: a video earns 9+ when it is well-sourced + didactic + dense + (depth OR originality OR uncommon rigor). Cites 4 known external references as qualitative anchors (Veritasium, 3Blue1Brown, Bellingcat, senior practitioner). - Opposite reinforcement: do not inflate. 5–6 is fine for competent median content. 7 is the floor of "good", not the ceiling.
- Mascot animation:
transform-origin: 50% 50%+transform-box: fill-box— fixes the rotation invisibility from the first attempt with pixel-based origin, which became a bounding-box offset instead of an SVG user-space coordinate. ChannelRankCardnow derives the seal frombayesian_scoreviasealForTotal()and renders a 44px animated mascot./extensionpage reads the version directly frommanifest.json(single source of truth).- Score formula visible in the methodology updated to reflect the new weights (clarity 30%, originality 10%).
- Brand repositioning: "popularity ≠ quality" as the editorial pillar. Home headline, tagline, SEO meta titles/descriptions, and methodology intro now articulate the differential: TubeLens contests YouTube's popularity metric and prioritises actual quality. Applied in pt/en/es.
- New "Why TubeLens?" section on the home (3 cards: the algorithm optimises views / we optimise quality / you decide with the right metric).
Fixed
- Anti-duplicate cache on
/api/analyze: defensive re-check before INSERT + handling ofunique_violation(23505) as cache hit (instead of 500). - Vercel Preview empty env vars caused
staging.tubelens.app/[locale]to return 500 due to SSR Supabase crash. 3 of 4 vars copied to Preview;SUPADATA_API_KEYleft for manual setup. - Extension zip download returned 404: the
proxy.tsmatcher (next-intl) didn't exclude.zip, so/tubelens-extension.zipwas being redirected to/pt/tubelens-extension.zip(which doesn't exist). Addedzip|pdf|woff2?to the extension allowlist.
Removed
- Google OAuth login and references to
auth_extra/linked_*in i18n — avoids a dependency on a Google Cloud Console project.
Security
support@inosx.comlinked to the GitHub accountm2f0(Settings → Emails) unblocks Vercel deploys for commits authored as INOSX.
Added
- AI-driven critical analysis of YouTube videos: 4 dimensions (density 30%, clarity 25%, credibility 30%, originality 15%) + 28 multilabel signals with intensity 1–5 and justification.
- Global cache by
video_idinvideo_analyses(PK enforces uniqueness in the database). - Transcript cache with 24h TTL in
transcript_cache— avoids burning transcript credits on retries. - Pages
/channels,/channel/[handle],/rankings/{channels,videos}with Bayesian smoothing (C=5, threshold 5.0, minimum 3 videos), periods week/month/year/all with ↑↓ movement. - i18n pt/en/es via
next-intland the[locale]segment. - Full admin:
/admin/{overview,users,analyses,channels,audit}+admin_audit_logtable. - Chrome extension v0.2.1: one-click analysis from any
youtube.com/watch?v=…page, popup with score and seal, share-as-comment (gated to Pro), localisation via_locales/. - SEO / GEO: dynamic
sitemap.ts(statics + channels + analyses × 3 locales with hreflang),robots.tspermitting 13 AI crawlers,llms.txt+llms-full.txt(Answer.AI spec), JSON-LD per type (WebApplication, TechArticle, Review, FAQPage), dynamic OpenGraph at/[locale]/analyze/[videoId]/opengraph-image.tsx, automatic IndexNow. - Server-side auth gates (
requireAuth(locale, currentPath)) on/channels,/rankings,/extension,/history,/account. Public pages kept:/,/methodology,/privacy,/pro,/auth/*. /accountpage with avatar upload (Supabase Storage with per-user-folder RLS), bio (280 chars), social handles (X, Facebook, Instagram, website).- Branded Supabase email templates (confirm, reset, magic link, invite, email change, verification code) — managed via
scripts/update-email-templates.mjs. - Account page + Header with avatar bubble.
Changed
- Analysis cache hardened against race conditions: defensive re-check of
video_analysesbefore INSERT + handling ofunique_violation(23505) as cache hit (instead of 500). - Vendor scrub: no user-facing string mentions Claude/Anthropic/Vercel/Supabase/Supadata/Postgres/Zod.
- Extension v0.2.1:
labelNamenow respects the popup locale (fixes pt/en mixing in shared text).
Removed
- Google OAuth login: removed to avoid a Google Cloud Console project dependency. Only email/password remained.
- Supadata
getVideocall: replaced by oEmbed (free), saves 1 credit per analysis.
Security
- RLS enabled on
profiles,video_analyses,user_video_analyses,channels,transcript_cache,admin_audit_log. avatarsbucket on Supabase Storage with write restricted bystorage.foldername(name)[1] = auth.uid()::text.