Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion public/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@

@import '../styles.css?v=171' layer(components);
@import '../antigravity.css?v=40' layer(legacy);
@import 'paper.css?v=19' layer(paper);
@import 'paper.css?v=20' layer(paper);
143 changes: 139 additions & 4 deletions public/css/paper.css
Original file line number Diff line number Diff line change
Expand Up @@ -757,8 +757,8 @@

/* ── Library index ───────────────────────────────────────────
The Library is an index of learner-owned concepts, not a promotional
empty-state card. Keep the first-use path compact and leave the existing
populated evidence cards untouched. */
card grid. Keep first use compact; populated Library is a quiet, divided
list that foregrounds learner reconstruction evidence. */

body.antigravity-theme #library-view {
min-height: calc(100dvh - 76px);
Expand All @@ -771,7 +771,7 @@ body.antigravity-theme #library-view {
}

.library-shell {
width: min(100%, 68rem);
width: min(100%, 56rem);
margin: 0 auto;
}

Expand Down Expand Up @@ -910,7 +910,117 @@ body.antigravity-theme #library-view {
}

.library-vault-grid {
margin-top: var(--space-5);
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 0;
margin-top: 0;
}

.library-card.library-card-vault {
display: block;
min-width: 0;
min-height: 0;
margin: 0;
padding: var(--space-5) var(--space-1);
overflow: visible;
border: 0;
border-bottom: 1px solid var(--border-subtle);
border-radius: 0;
background: transparent;
box-shadow: none;
backdrop-filter: none;
-webkit-backdrop-filter: none;
text-align: left;
transition: background-color var(--duration-micro) var(--ease-standard);
}

.library-card.library-card-vault::after {
content: none;
}

.library-card.library-card-vault:hover {
transform: none;
border-color: var(--border-subtle);
background: var(--accent-soft);
box-shadow: none;
}

.library-card.library-card-vault:active {
transform: none;
background: var(--accent-soft-strong);
}

.library-card.library-card-vault:focus-visible {
position: relative;
z-index: 1;
outline: none;
box-shadow: var(--accent-ring);
}

.library-card-header {
display: flex;
flex-wrap: wrap;
align-items: baseline;
justify-content: space-between;
gap: var(--space-2) var(--space-4);
margin: 0;
}

.library-card-header > div {
flex: 1 1 10rem;
min-width: 0;
}

.library-card-name {
display: block;
color: var(--text-strong);
font-family: var(--font-body);
font-size: var(--text-lg);
font-weight: 700;
line-height: var(--leading-snug);
letter-spacing: var(--tracking-tight);
overflow-wrap: anywhere;
}

.library-card-state {
flex: 0 0 auto;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
color: var(--primary-readable);
font-family: var(--font-body);
font-size: var(--text-xs);
font-weight: 600;
line-height: var(--leading-snug);
letter-spacing: 0;
text-transform: lowercase;
white-space: nowrap;
}

.library-card-state[data-state="needs repair"] {
color: color-mix(in srgb, var(--node-drilled) 50%, var(--text-strong));
background: transparent;
}

.library-card-state[data-state="solidified"] {
color: color-mix(in srgb, var(--node-solidified) 50%, var(--text-strong));
background: transparent;
}

.library-card-summary {
display: -webkit-box;
max-width: 65ch;
margin: var(--space-3) 0 0;
overflow: hidden;
color: var(--text-strong);
font-family: var(--font-body);
font-size: var(--text-sm);
line-height: var(--leading-normal);
text-wrap: pretty;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}

@media (max-width: 899px) {
Expand All @@ -936,11 +1046,36 @@ body.antigravity-theme #library-view {
justify-self: start;
padding: 0;
}

.library-card-header {
display: block;
}

.library-card-state {
display: block;
margin-top: var(--space-1);
white-space: normal;
}
}

@media (forced-colors: active) {
.library-card.library-card-vault:focus-visible {
outline: 2px solid Highlight;
outline-offset: -2px;
box-shadow: none;
}
}

/* ── Reduced motion ────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
.library-card.library-card-vault,
.library-card.library-card-vault:hover,
.library-card.library-card-vault:active {
transform: none;
transition: none;
}

.ig-button,
.ig-button:hover:not(:disabled),
.ig-button:focus-visible,
Expand Down
4 changes: 2 additions & 2 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=6">
<link rel="apple-touch-icon" href="/apple-touch-icon.png?v=6">
<link rel="shortcut icon" href="/favicon-32x32.png?v=6">
<link rel="stylesheet" href="/css/index.css?v=188">
<link rel="stylesheet" href="/css/index.css?v=189">
<link rel="stylesheet" href="/css/drill-chamber.css?v=17">
</head>

Expand Down Expand Up @@ -435,7 +435,7 @@ <h2 class="concept-header-title" id="concept-header-title"></h2>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>

<script src="/js/drill-chamber.js?v=18"></script>
<script type="module" src="/js/app.js?v=205"></script>
<script type="module" src="/js/app.js?v=206"></script>
<script type="module" src="/js/floating-room-label.js?v=10"></script>
<script type="module" src="/js/iso-board-state-surface.js?v=10"></script>
<script type="module" src="/js/feedback.js?v=6"></script>
Expand Down
2 changes: 1 addition & 1 deletion public/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import {
persistPhaseBResumeState as persistStoredPhaseBResumeState,
persistPhaseBSessionState as persistStoredPhaseBSessionState,
} from './phase-b-session.js';
import { buildLibraryHtml } from './library-view.js?v=5';
import { buildLibraryHtml } from './library-view.js?v=6';
import { createTrainingStore, TRAINING_SCHEMA_VERSION, TRAINING_STORE_KEY_PREFIX } from './training-store.js';
import {
hydrateAndSyncLearnerState,
Expand Down
26 changes: 2 additions & 24 deletions public/js/library-view.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { escHtml } from './html.js';
import { deriveConceptBadge, parseConceptGraphData } from './concept-status.js';
import { deriveConceptBadge } from './concept-status.js';

const EMPTY_RECONSTRUCTION_COPY = 'Your first reconstruction will appear here.';

Expand Down Expand Up @@ -44,28 +44,13 @@ export function getBestLearnerAttempt(training) {
})[0];
}

export function getLibraryConceptMeta(concept, training = null) {
const graph = parseConceptGraphData(concept);

const metadata = graph?.metadata || {};
const clusters = Array.isArray(graph?.clusters) ? graph.clusters : [];
const subnodeCount = clusters.reduce((total, cluster) => total + ((cluster.subnodes || []).length), 0);
export function getLibraryConceptMeta(_concept, training = null) {
const bestAttempt = getBestLearnerAttempt(training);
const thesis = bestAttempt?.user_text || EMPTY_RECONSTRUCTION_COPY;
const sourceLabel = concept.contentFilename
? `Source: ${concept.contentFilename}`
: concept.contentType
? `Source: ${concept.contentType.toUpperCase()}`
: (metadata.source_title ? `Map: ${metadata.source_title}` : 'Draft map');

return {
thesis: thesis.length > 180 ? `${thesis.slice(0, 177).trimEnd()}...` : thesis,
summarySource: bestAttempt ? 'learner_attempt' : 'none',
architecture: metadata.architecture_type ? metadata.architecture_type.replace(/_/g, ' ') : null,
difficulty: metadata.difficulty || null,
clusterCount: clusters.length,
subnodeCount,
sourceLabel,
};
}

Expand Down Expand Up @@ -115,18 +100,11 @@ export function buildLibraryHtml(concepts, trainingByConceptId = {}, options = {
<div class="library-card library-card-vault" role="button" tabindex="0" aria-label="Open concept ${escHtml(conceptName)}" data-state="${escHtml(derivedState)}" data-concept-id="${escHtml(conceptId)}" style="cursor:pointer;" onclick="App.openLibraryConcept(this.dataset.conceptId)" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();App.openLibraryConcept(this.dataset.conceptId)}">
<div class="library-card-header">
<div>
<div class="library-card-kicker">${escHtml(meta.sourceLabel)}</div>
<span class="library-card-name">${escHtml(conceptName)}</span>
</div>
${stateBadge}
</div>
<p class="library-card-summary">${escHtml(meta.thesis)}</p>
<div class="library-card-meta">
${meta.architecture ? `<span class="library-card-pill">${escHtml(meta.architecture)}</span>` : ''}
<span class="library-card-pill">${escHtml(`${meta.clusterCount} ${meta.clusterCount === 1 ? 'section' : 'sections'}`)}</span>
<span class="library-card-pill">${escHtml(`${meta.subnodeCount} ${meta.subnodeCount === 1 ? 'entry' : 'entries'}`)}</span>
</div>
<div class="library-card-cta">Open concept</div>
</div>`;
}).join('') + `</div>`;
}
Expand Down
14 changes: 3 additions & 11 deletions tests/e2e/test_app_helper_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,16 +475,7 @@ def test_app_helper_modules_preserve_browser_contracts(clean_page: Page, base_ur
same(library.getLibraryConceptMeta({ name: 'Concept', state: 'growing', graphData: graph }), {
thesis: 'Your first reconstruction will appear here.',
summarySource: 'none',
architecture: 'cause effect',
difficulty: 'medium',
clusterCount: 2,
subnodeCount: 3,
sourceLabel: 'Map: Source Title',
}, 'library metadata');
assert(library.getLibraryConceptMeta({
contentFilename: 'notes.md',
graphData: JSON.stringify({ metadata: { core_thesis: 'From file' }, clusters: [] }),
}).sourceLabel === 'Source: notes.md', 'library filename source');
const training = {
node_records: {
n1: {
Expand Down Expand Up @@ -523,8 +514,9 @@ def test_app_helper_modules_preserve_browser_contracts(clean_page: Page, base_ur
assert(libraryHtml.includes('&lt;Unsafe&gt;'), 'library escaped name');
assert(libraryHtml.includes('Learner-owned reconstruction.'), 'library card learner evidence');
assert(!libraryHtml.includes('This is the central claim.'), 'library card no AI thesis fallback');
assert(libraryHtml.includes('2 sections'), 'library section count');
assert(libraryHtml.includes('3 entries'), 'library entry count');
assert(!libraryHtml.includes('library-card-kicker'), 'library has no source kicker');
assert(!libraryHtml.includes('library-card-meta'), 'library has no structure metadata');
assert(!libraryHtml.includes('library-card-cta'), 'library row needs no duplicate CTA');
window.App.showLibrary();
assert(document.getElementById('library-view').classList.contains('visible'), 'library view visible');
assert(document.getElementById('library-content').textContent.includes('Your first reconstruction starts here'), 'library app path');
Expand Down
26 changes: 25 additions & 1 deletion tests/e2e/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ def test_library_card_uses_training_evidence_not_ai_summary(
page: Page, base_url: str
) -> None:
"""Library summaries must be learner evidence, not generated source text."""
page.set_viewport_size({"width": 390, "height": 844})
_enter_app_shell_as_guest(page, base_url)
page.evaluate("localStorage.clear(); sessionStorage.clear();")
_seed_training_truth_concept(page)
Expand All @@ -563,7 +564,7 @@ def test_library_card_uses_training_evidence_not_ai_summary(
sidebar_dot = page.locator(".concept-item", has_text="Training Truth QA").locator(".concept-dot")
expect(sidebar_dot).to_have_attribute("data-state", "primed")

page.locator("#nav-library").click()
page.locator("#bn-library").click()
card = page.locator(".library-card-vault", has_text="Training Truth QA")
expect(card).to_be_visible()
expect(card).to_have_attribute("data-state", "primed")
Expand All @@ -574,6 +575,29 @@ def test_library_card_uses_training_evidence_not_ai_summary(
expect(card).not_to_contain_text("growing")
expect(card).not_to_contain_text("AI GENERATED CORE THESIS SHOULD NOT APPEAR")
expect(card).not_to_contain_text("SOURCE PREVIEW SHOULD NOT APPEAR")
expect(card.locator(".library-card-kicker")).to_have_count(0)
expect(card.locator(".library-card-meta")).to_have_count(0)
expect(card.locator(".library-card-cta")).to_have_count(0)

card_style = card.evaluate(
"""element => {
const style = getComputedStyle(element);
return {
backgroundImage: style.backgroundImage,
borderRadius: style.borderRadius,
boxShadow: style.boxShadow,
};
}"""
)
assert card_style == {
"backgroundImage": "none",
"borderRadius": "0px",
"boxShadow": "none",
}
card_box = card.bounding_box()
assert card_box is not None
assert card_box["height"] < 170
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")


def test_library_training_render_survives_one_corrupt_record(
Expand Down
22 changes: 3 additions & 19 deletions tests/test_frontend_app_helper_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -766,20 +766,8 @@ def test_library_view_helpers_preserve_card_metadata_and_empty_state() -> None:
{
thesis: 'Your first reconstruction will appear here.',
summarySource: 'none',
architecture: 'cause effect',
difficulty: 'medium',
clusterCount: 2,
subnodeCount: 3,
sourceLabel: 'Map: Source Title',
}
);
assert.equal(
getLibraryConceptMeta({
contentType: 'pdf',
graphData: JSON.stringify({ metadata: { thesis: 'x'.repeat(200) }, clusters: [] }),
}).sourceLabel,
'Source: PDF'
);
assert.ok(getLibraryConceptMeta({ graphData: '{' }).thesis.includes('first reconstruction'));

const training = {
Expand Down Expand Up @@ -807,11 +795,6 @@ def test_library_view_helpers_preserve_card_metadata_and_empty_state() -> None:
{
thesis: 'The learner reconstructed the causal mechanism.',
summarySource: 'learner_attempt',
architecture: 'cause effect',
difficulty: 'medium',
clusterCount: 2,
subnodeCount: 3,
sourceLabel: 'Map: Source Title',
}
);

Expand Down Expand Up @@ -841,8 +824,9 @@ def test_library_view_helpers_preserve_card_metadata_and_empty_state() -> None:
assert.ok(cardHtml.includes('&lt;Unsafe&gt;'));
assert.ok(cardHtml.includes('The learner reconstructed the causal mechanism.'));
assert.ok(!cardHtml.includes('This is the central claim.'));
assert.ok(cardHtml.includes('2 sections'));
assert.ok(cardHtml.includes('3 entries'));
assert.ok(!cardHtml.includes('library-card-kicker'));
assert.ok(!cardHtml.includes('library-card-meta'));
assert.ok(!cardHtml.includes('library-card-cta'));

const noEvidenceHtml = buildLibraryHtml([
{ id: 'legacy-solid', name: 'Legacy Solid', state: 'actualized', graphData: graph },
Expand Down
Loading