Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
acbb385
docs(design): custody commit — recovered 2026-07-03 reader IA study +…
Jesssullivan Jul 15, 2026
e7ed2c2
feat(reader): harvest #217 donor primitives verbatim (TierBadge, blen…
Jesssullivan Jul 15, 2026
b47be05
feat(reader): pure tier-partition + constellation-group model (TIN-2903)
Jesssullivan Jul 15, 2026
434e3ff
feat(reader): oscillating heron/constellation masthead (TIN-2903)
Jesssullivan Jul 15, 2026
2093275
feat(reader): register, pulse-rail, and constellation-section compone…
Jesssullivan Jul 15, 2026
856bc7f
feat(reader): make "/" the Observatory Ledger reader composition (TIN…
Jesssullivan Jul 15, 2026
20bb420
feat(reader): reduce nav, add footer More group + conditional build-s…
Jesssullivan Jul 15, 2026
a47d8df
fix(reader): complete #217 BlogCard tier-badge harvest + explicit mas…
Jesssullivan Jul 15, 2026
fef98d7
feat(reader): constellation on load, full-band field, visibility-gate…
Jesssullivan Jul 16, 2026
41660ed
test(e2e): update homepage + nav specs to the ratified '/ is home' co…
Jesssullivan Jul 17, 2026
2c7ec75
feat(reader): hydrate pulse rail from the live broker snapshot (TIN-2…
Jesssullivan Jul 17, 2026
a2f35aa
fix(reader): semantic honesty — real h1, single banner landmark, mach…
Jesssullivan Jul 17, 2026
a2c403c
feat(reader): interactive constellation — hover titles, click-to-navi…
Jesssullivan Jul 17, 2026
bd4c772
feat(reader): position the star tooltip with Floating UI (TIN-2903 M1…
Jesssullivan Jul 17, 2026
b0ee3cd
fix(bazel): expose Floating UI through rules_js lock
Jesssullivan Jul 17, 2026
abe8a0e
test(reader): SSR-state + counterpart-axes + node-metadata coverage (…
Jesssullivan Jul 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
366 changes: 366 additions & 0 deletions docs/design/observatory-ledger-design-of-record-2026-07-15.html

Large diffs are not rendered by default.

1,236 changes: 1,236 additions & 0 deletions docs/design/reader-ia-study-2026-07-03.html

Large diffs are not rendered by default.

19 changes: 14 additions & 5 deletions e2e/homepage.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { test, expect } from '@playwright/test';

test.describe('Homepage redirect', () => {
test('/ redirects to /blog', async ({ page }) => {
// TIN-2903 ratified contract: `/` IS the canonical reader homepage (the old
// meta-refresh redirect to /blog is retired); /blog remains the archive.
test.describe('Reader homepage', () => {
test('/ serves the reader without redirecting', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveURL(/\/blog/);
await expect(page).toHaveURL(/\/$/);
await expect(page.locator('section.observatory-masthead')).toBeVisible();
});

test('/ has canonical pointing to /blog', async ({ page }) => {
test('/ has canonical pointing to the apex root', async ({ page }) => {
await page.goto('/');
const canonical = page.locator('link[rel="canonical"]');
await expect(canonical).toHaveAttribute('href', /\/blog/);
await expect(canonical).toHaveAttribute('href', 'https://transscendsurvival.org/');
});

test('/blog still serves the archive listing', async ({ page }) => {
await page.goto('/blog');
await expect(page).toHaveURL(/\/blog/);
await expect(page.getByRole('heading', { name: 'Blog' })).toBeVisible();
});
});
7 changes: 5 additions & 2 deletions e2e/visual-qa.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@ test.describe('Homepage — visual QA across viewports', () => {

test('navigation is accessible', async ({ page }) => {
if (vp.width >= 768) {
// Desktop nav: visible inline links (md: breakpoint = 768px)
// Desktop nav: visible inline links (md: breakpoint = 768px).
// TIN-2903 nav reduction: the Blog link is retired (wordmark → /),
// so assert a surviving primary link and the Blog link's absence.
const desktopNav = page.locator('nav.hidden.md\\:flex');
await expect(desktopNav).toBeVisible();
await expect(desktopNav.getByRole('link', { name: 'Blog' })).toBeVisible();
await expect(desktopNav.getByRole('link', { name: 'Photography' })).toBeVisible();
await expect(desktopNav.getByRole('link', { name: 'Blog' })).toHaveCount(0);
} else {
// Mobile: hamburger button present
const hamburger = page.getByRole('button', { name: 'Open navigation' });
Expand Down
25 changes: 13 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"vitest": "^4.1.4"
},
"dependencies": {
"@floating-ui/dom": "^1.8.0",
"@fontsource/crimson-pro": "^5.2.8",
"@fontsource/fira-code": "^5.2.7",
"@fontsource/inter": "^5.2.8",
Expand Down
30 changes: 19 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/app.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
// Public build SHA injected by Vite `define` (see vite.config.ts). Empty
// string when no real build sha is present.
const __BUILD_SHA__: string;

namespace App {
// interface Error {}
// interface Locals {}
Expand Down
7 changes: 6 additions & 1 deletion src/lib/components/BlogCard.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import type { Post } from '$lib/types';
import TierBadge from '$lib/components/TierBadge.svelte';

// dimensions.json is generated by scripts/optimize-images.mjs at prebuild.
// Use import.meta.glob so the build doesn't fail if the file is missing.
Expand Down Expand Up @@ -73,9 +74,12 @@
</article>
{:else if post}
<article
data-tier={post.editorial_tier ?? undefined}
class="card glass p-0 overflow-hidden hover:ring-2 ring-primary-500 transition-all group {post.featured
? 'ring-1 ring-primary-300'
: ''}"
: post.editorial_tier === 'noteworthy'
? 'ring-1 ring-primary-400/50'
: ''}"
>
<a href="/blog/{post.slug}" class="block">
<!-- Feature image banner -->
Expand Down Expand Up @@ -137,6 +141,7 @@
>{post.category}</span
>
{/if}
<TierBadge tier={post.editorial_tier} />
</div>

<!-- Title -->
Expand Down
38 changes: 38 additions & 0 deletions src/lib/components/TierBadge.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
import type { PostEditorialTier } from '$lib/types';

// Reader-weight badge for the editorial taxonomy (docs/blog-editorial-taxonomy-2026-07-03.md).
// Tiers are IA/reader-weight ONLY — never an auth, privacy, or AP-delivery signal.
// Renders nothing when the tier is undefined ("not classified yet").
let { tier, class: klass = '' }: { tier?: PostEditorialTier | undefined; class?: string } = $props();

const TIER_LABELS: Record<PostEditorialTier, string> = {
noteworthy: 'Noteworthy',
'less-noteworthy': 'Less noteworthy',
};

// noteworthy gets a stronger accent tint; less-noteworthy stays muted/outlined.
const TIER_PRESETS: Record<PostEditorialTier, string> = {
noteworthy: 'preset-tonal-primary',
'less-noteworthy': 'preset-outlined-surface-500',
};
</script>

{#if tier}
<span
class="badge {TIER_PRESETS[tier]} text-xs font-medium {klass}"
data-tier={tier}
data-testid="tier-badge"
aria-label={`Editorial tier: ${TIER_LABELS[tier]}`}
title={`Editorial tier: ${TIER_LABELS[tier]}`}
>
{#if tier === 'noteworthy'}
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"
><path
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
/></svg
>
{/if}
{TIER_LABELS[tier]}
</span>
{/if}
74 changes: 74 additions & 0 deletions src/lib/components/TierBadge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, expect, it } from 'vitest';
import { render } from 'svelte/server';
import type { Post } from '$lib/types';
import { POST_EDITORIAL_TIERS } from '$lib/types';
import TierBadge from './TierBadge.svelte';
import BlogCard from './BlogCard.svelte';

describe('TierBadge', () => {
it('renders a labelled badge for the noteworthy tier', () => {
const { html } = render(TierBadge, { props: { tier: 'noteworthy' } });

expect(html).toContain('data-testid="tier-badge"');
expect(html).toContain('data-tier="noteworthy"');
expect(html).toContain('Noteworthy');
expect(html).toContain('aria-label="Editorial tier: Noteworthy"');
});

it('renders a labelled badge for the less-noteworthy tier', () => {
const { html } = render(TierBadge, { props: { tier: 'less-noteworthy' } });

expect(html).toContain('data-testid="tier-badge"');
expect(html).toContain('data-tier="less-noteworthy"');
expect(html).toContain('Less noteworthy');
expect(html).toContain('aria-label="Editorial tier: Less noteworthy"');
});

it('renders a badge with an aria-label for every declared tier', () => {
for (const tier of POST_EDITORIAL_TIERS) {
const { html } = render(TierBadge, { props: { tier } });
expect(html).toContain('aria-label="Editorial tier:');
expect(html).toContain(`data-tier="${tier}"`);
}
});

it('renders nothing when the tier is undefined', () => {
const { html } = render(TierBadge, { props: { tier: undefined } });

expect(html).not.toContain('tier-badge');
expect(html).not.toContain('aria-label');
expect(html.replace(/<!--[\s\S]*?-->/g, '').trim()).toBe('');
});
});

function makePost(overrides: Partial<Post> = {}): Post {
return {
title: 'A Test Post',
slug: 'a-test-post',
date: '2026-03-04',
description: 'A description for the card.',
tags: ['test'],
published: true,
...overrides,
};
}

describe('BlogCard editorial tier surface', () => {
it('shows the tier badge when the post carries a tier', () => {
const { html } = render(BlogCard, {
props: { post: makePost({ editorial_tier: 'noteworthy' }) },
});

expect(html).toContain('data-testid="tier-badge"');
expect(html).toContain('aria-label="Editorial tier: Noteworthy"');
});

it('omits the tier badge entirely when the post has no tier', () => {
const { html } = render(BlogCard, {
props: { post: makePost() },
});

expect(html).toContain('A Test Post');
expect(html).not.toContain('tier-badge');
});
});
9 changes: 7 additions & 2 deletions src/lib/components/pulse/PulseBirdCard.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import type { PublicPulseItem } from '@blog/pulse-core/schema';
import TierBadge from '$lib/components/TierBadge.svelte';

let { item }: { item: PublicPulseItem } = $props();

Expand All @@ -14,8 +15,12 @@
</script>

<article class="card preset-outlined-success-500 p-4 space-y-2">
<header class="flex items-center justify-between text-sm text-surface-600-400">
<span aria-label="kind">🐦 bird sighting</span>
<header class="flex items-center justify-between gap-3 text-sm text-surface-600-400">
<span class="flex items-center gap-2">
<span aria-label="kind">🐦 bird sighting</span>
<!-- Reader-weight tier, display-only. Renders nothing when salience is absent. -->
<TierBadge tier={item.salience} />
</span>
<time datetime={item.occurredAt}>{formatted}</time>
</header>
{#if sighting}
Expand Down
24 changes: 24 additions & 0 deletions src/lib/components/pulse/PulseCards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,30 @@ describe('Pulse cards', () => {
});
});

describe('Pulse card editorial salience surface', () => {
it('shows a TierBadge on a note card only when salience is present', () => {
const withSalience = render(PulseNoteCard, {
props: { item: { ...noteItem, salience: 'noteworthy' } },
});
expect(withSalience.html).toContain('data-testid="tier-badge"');
expect(withSalience.html).toContain('aria-label="Editorial tier: Noteworthy"');

const withoutSalience = render(PulseNoteCard, { props: { item: noteItem } });
expect(withoutSalience.html).not.toContain('tier-badge');
});

it('shows a TierBadge on a bird card only when salience is present', () => {
const withSalience = render(PulseBirdCard, {
props: { item: { ...birdItem, salience: 'less-noteworthy' } },
});
expect(withSalience.html).toContain('data-testid="tier-badge"');
expect(withSalience.html).toContain('data-tier="less-noteworthy"');

const withoutSalience = render(PulseBirdCard, { props: { item: birdItem } });
expect(withoutSalience.html).not.toContain('tier-badge');
});
});

describe('Pulse lab policy result rendering', () => {
it('renders allowed projection decisions', () => {
const decision: PolicyDecision = { allowed: true, item: noteItem };
Expand Down
Loading
Loading