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
15 changes: 14 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,22 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="description" content="Wikipedia Knowledge Map - An interactive visualization of 250,000 Wikipedia articles, their semantic relationships, and difficulty levels.">
<meta name="description" content="An interactive tool that maps out everything you know. Answer questions and watch your personalized knowledge map take shape.">
<title>Knowledge Mapper</title>

<!-- Open Graph meta tags -->
<meta property="og:title" content="Knowledge Mapper">
<meta property="og:description" content="An interactive tool that maps out everything you know. Answer questions and watch your personalized knowledge map take shape.">
<meta property="og:image" content="https://context-lab.com/mapper/og-preview.png">
<meta property="og:url" content="https://context-lab.com/mapper/">
<meta property="og:type" content="website">

<!-- Twitter Card meta tags -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Knowledge Mapper">
<meta name="twitter:description" content="An interactive tool that maps out everything you know. Answer questions and watch your personalized knowledge map take shape.">
<meta name="twitter:image" content="https://context-lab.com/mapper/og-preview.png">

<!-- KaTeX for LaTeX rendering -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css" integrity="sha384-5TcZemv2l/9On385z///+d7MSYlvIEw9FuZTIdZ14vJLqWphw7e7ZPuOiCHJcFCP" crossorigin="anonymous" onerror="window.__katexFailed=true; console.error('KaTeX CSS failed to load'); document.getElementById('cdn-warning').hidden=false;">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js" integrity="sha384-cMkvdD8LoxVzGF/RPUKAcvmm49FQ0oxwDF3BGKtDXcEc+T1b2N+teh/OJfpU0jr6" crossorigin="anonymous" onerror="window.__katexFailed=true; console.error('KaTeX JS failed to load'); document.getElementById('cdn-warning').hidden=false;"></script>
Expand Down
21 changes: 17 additions & 4 deletions package-lock.json

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

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"dependencies": {
"@nanostores/persistent": "^1.3.3",
"deck.gl": "^9.2.7",
"nanostores": "^1.1.0"
"nanostores": "^1.1.0",
"pako": "^2.1.0"
},
"devDependencies": {
"@playwright/test": "^1.58.2",
Expand Down
Binary file added public/og-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions specs/008-shareable-map-links/checklists/comprehensive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Requirements Quality Checklist: Shareable Map Links

**Purpose**: Comprehensive requirements quality validation — PR review gate after implementation
**Created**: 2026-03-12
**Feature**: [spec.md](../spec.md)
**Depth**: Standard (~30 items)
**Audience**: PR reviewer

## Requirement Completeness

- [ ] CHK001 - Are encoding value semantics defined for all possible response states (correct, incorrect, skipped, unanswered)? [Completeness, Spec §FR-002]
- [ ] CHK002 - Is the binary wire format fully specified with byte offsets, endianness, and field sizes? [Completeness, Contract §token-format]
- [ ] CHK003 - Are requirements for the "Copy Link" button placement and styling within the share modal specified? [Gap, Spec §FR-009]
- [ ] CHK004 - Is the CTA button's position, styling, and responsive behavior in the shared view defined? [Gap, Contract §url-contract]
- [ ] CHK005 - Are requirements specified for all 5 social platform share buttons (LinkedIn, X, Bluesky, Facebook, Instagram)? [Completeness, Spec §FR-015]
- [ ] CHK006 - Are loading/progress state requirements defined for the shared view while the GP estimator runs? [Gap]

## Requirement Clarity

- [ ] CHK007 - Is "minimal chrome" quantified with an explicit list of hidden vs. visible UI elements? [Clarity, Spec §FR-006]
- [ ] CHK008 - Is "visually identical" (SC-005) defined with measurable criteria (pixel diff threshold, screenshot comparison method)? [Ambiguity, Spec §SC-005]
- [ ] CHK009 - Is the "stable, deterministic index" sort order precisely defined (sort key, tie-breaking, collation)? [Clarity, Spec §FR-001]
- [ ] CHK010 - Is "gracefully handle" for invalid tokens defined with specific fallback behavior? [Clarity, Spec §FR-011]
- [ ] CHK011 - Is the OG preview image content specified with enough detail for a designer (text content, font sizes, positioning, contrast requirements)? [Clarity, Spec §FR-018]
- [ ] CHK012 - Is the Instagram "prompt to paste" UX described with specific wording, duration, and dismissal behavior? [Clarity, Spec §FR-020]

## Requirement Consistency

- [ ] CHK013 - Does the "read-only" requirement in US2 align with the "minimal chrome" clarification listing hidden elements? [Consistency, Spec §US2 vs §FR-006]
- [ ] CHK014 - Are the URL size guarantees in the token format contract consistent with FR-014 (under 2000 chars for ≤200 answers)? [Consistency, Contract §token-format vs Spec §FR-014]
- [ ] CHK015 - Is the terminology "token" used consistently across spec, plan, contracts, and tasks (not mixed with "hash", "code", "key")? [Consistency]
- [ ] CHK016 - Does the social platform list match across US3 acceptance scenarios, FR-015, FR-019, and SC-007 (all must list the same 5 platforms)? [Consistency]

## Acceptance Criteria Quality

- [ ] CHK017 - Can SC-001 ("generate shareable link in under 2 seconds") be objectively measured with a defined starting and ending event? [Measurability, Spec §SC-001]
- [ ] CHK018 - Can SC-002 ("fully rendered knowledge map within 3 seconds") be measured — is "fully rendered" defined (first paint? all dots visible? estimator complete?)? [Measurability, Spec §SC-002]
- [ ] CHK019 - Can SC-004 ("tokens remain decodable after question bank updates") be verified with a defined test procedure? [Measurability, Spec §SC-004]
- [ ] CHK020 - Can SC-007 ("link previews display correct title, description, and preview image") be verified — are "correct" values specified? [Measurability, Spec §SC-007]

## Scenario Coverage

- [ ] CHK021 - Are requirements defined for the zero-response state in shared view (user shares before answering any questions)? [Coverage, Edge Case]
- [ ] CHK022 - Are requirements specified for what happens when a shared URL is opened on an unsupported/old browser? [Coverage, Gap]
- [ ] CHK023 - Are requirements defined for the shared view when the "all" domain bundle fails to load (network error)? [Coverage, Exception Flow]
- [ ] CHK024 - Are requirements specified for concurrent sharing scenarios (user generates link, answers more questions, then recipient opens the old link)? [Coverage, Alternate Flow]

## Edge Case Coverage

- [ ] CHK025 - Does the spec define behavior when URL contains both `?t=TOKEN` and other query parameters (e.g., UTM tracking)? [Edge Case, Spec §Edge Cases]
- [ ] CHK026 - Are requirements defined for token URLs that pass through URL shorteners (bit.ly, t.co) — does the token survive? [Edge Case, Gap]
- [ ] CHK027 - Is behavior specified for extremely long tokens (all 2500 questions answered) on platforms with URL length limits? [Edge Case, Spec §Edge Cases]
- [ ] CHK028 - Are requirements defined for what happens if pako (compression library) fails to load or is unavailable? [Edge Case, Gap]

## Non-Functional Requirements

- [ ] CHK029 - Are accessibility requirements specified for the shared view CTA button (keyboard focus, screen reader label, contrast)? [Gap, Accessibility]
- [ ] CHK030 - Are performance requirements defined for token encoding/decoding operations (max latency on mid-range hardware)? [Gap, Performance]
- [ ] CHK031 - Are privacy considerations documented — do tokens reveal any personally identifiable information? [Gap, Privacy]
- [ ] CHK032 - Are WCAG AA color contrast requirements specified for the CTA button and shared view elements? [Gap, Accessibility]

## Dependencies & Assumptions

- [ ] CHK033 - Is the assumption "social platforms support URLs up to 2000 characters" validated for all 5 target platforms? [Assumption, Spec §Assumptions]
- [ ] CHK034 - Is the pako dependency version-pinned, and are fallback requirements specified if the CDN or npm package is unavailable? [Dependency, Gap]
- [ ] CHK035 - Is the assumption "GitHub Pages serves static OG meta tags correctly to social crawlers" validated? [Assumption, Spec §Assumptions]

## Notes

- This checklist validates the REQUIREMENTS quality, not the implementation
- Items marked [Gap] indicate missing requirements that should be added before final PR approval
- Items marked [Ambiguity] indicate requirements that need sharper definition
- Each item should be resolved by updating spec.md, not by verbal agreement
36 changes: 36 additions & 0 deletions specs/008-shareable-map-links/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Specification Quality Checklist: Shareable Map Links

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-12
**Feature**: [spec.md](../spec.md)

## Content Quality

- [X] No implementation details (languages, frameworks, APIs)
- [X] Focused on user value and business needs
- [X] Written for non-technical stakeholders
- [X] All mandatory sections completed

## Requirement Completeness

- [X] No [NEEDS CLARIFICATION] markers remain
- [X] Requirements are testable and unambiguous
- [X] Success criteria are measurable
- [X] Success criteria are technology-agnostic (no implementation details)
- [X] All acceptance scenarios are defined
- [X] Edge cases are identified
- [X] Scope is clearly bounded
- [X] Dependencies and assumptions identified

## Feature Readiness

- [X] All functional requirements have clear acceptance criteria
- [X] User scenarios cover primary flows
- [X] Feature meets measurable outcomes defined in Success Criteria
- [X] No implementation details leak into specification

## Notes

- All open questions from the issue were resolved via the author's comment (read-only shared views, "All (general)" domain only, no watched-video state in tokens)
- FR-013 explicitly prevents the "separate load page" drift risk raised in the issue comments
- Compression/encoding approach mentioned in Assumptions section is descriptive context, not prescriptive implementation detail
64 changes: 64 additions & 0 deletions specs/008-shareable-map-links/contracts/token-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Contract: Response Token Binary Format

**Version**: 1 | **Date**: 2026-03-12

## Wire Format

```text
Byte 0: version (uint8) — currently 0x01
Bytes 1-2: count (uint16 big-endian) — number of response entries
Bytes 3+: entries, each 3 bytes:
[0-1] index (uint16 big-endian) — question index
[2] value (int8) — response value
```

## Value Encoding

| Response | Encoded Value | Byte Representation |
|-|-|-|
| Correct | 2 | 0x02 |
| Skipped | 1 | 0x01 |
| Incorrect | -1 | 0xFF |
| Unanswered | 0 | (not stored) |

## Compression

1. Serialize to binary using the wire format above
2. Compress with raw DEFLATE (pako `deflate` with `raw: true`)
3. Encode compressed bytes as base64url (RFC 4648 §5): `+` → `-`, `/` → `_`, strip `=` padding

## URL Format

```text
https://context-lab.com/mapper/?t={base64url_token}
```

## Size Guarantees

| Answered Questions | Raw Bytes | Compressed (est.) | Base64url Chars | Total URL Length |
|-|-|-|-|-|
| 50 | 153 | ~100 | ~134 | ~175 |
| 100 | 303 | ~200 | ~268 | ~309 |
| 200 | 603 | ~380 | ~508 | ~549 |
| 500 | 1503 | ~800 | ~1068 | ~1109 |
| 2500 (all) | 7503 | ~3000 | ~4000 | ~4041 |

Base URL (`https://context-lab.com/mapper/?t=`) = 41 characters.

## Decoding Rules

1. Extract `t` parameter from URL query string
2. Restore base64url: `-` → `+`, `_` → `/`, re-pad with `=` to multiple of 4
3. Decode base64 to bytes
4. Inflate with pako (`inflate` with `raw: true`)
5. Parse version byte — if unsupported version, abort (fall back to normal app)
6. Parse count (bytes 1-2, big-endian)
7. For each entry: read index (uint16 BE), value (int8)
8. Look up question_id from QuestionIndex using the token's version
9. Skip entries whose index has no matching question (question was removed)

## Versioning

- Version byte `0x01`: Initial format as described above
- Future versions may change field sizes or add metadata
- Decoders MUST check the version byte and reject unknown versions gracefully
44 changes: 44 additions & 0 deletions specs/008-shareable-map-links/contracts/url-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Contract: URL Parameter Interface

**Version**: 1 | **Date**: 2026-03-12

## Query Parameters

| Parameter | Type | Required | Description |
|-|-|-|-|
| `t` | string (base64url) | No | Encoded response token. When present, app boots in shared view mode. |

## Behavior Matrix

| URL | Behavior |
|-|-|
| `/mapper/` | Normal app — landing screen, full UI |
| `/mapper/?t={valid_token}` | Shared view — minimal chrome, read-only map |
| `/mapper/?t=` | Normal app (empty token treated as absent) |
| `/mapper/?t={invalid}` | Normal app (decode failure → silent fallback) |
| `/mapper/?t={valid}&other=param` | Shared view (extra params ignored) |

## Shared View Mode

When a valid `?t=` token is detected:

1. **Skip** landing/welcome screen
2. **Load** "All (general)" domain bundle
3. **Decode** token into SyntheticResponse array
4. **Run** GP estimator with SyntheticResponses
5. **Render** map with heatmap + response dots
6. **Show** minimal chrome: map canvas + "Map your *own* knowledge!" CTA button
7. **Hide** header toolbar, quiz panel, video panel, minimap, drawer pulls

## CTA Button

- Text: "Map your *own* knowledge!"
- Action: Navigate to `/mapper/` (no token — starts fresh normal session)
- Position: Fixed bottom-center, visually prominent
- Style: Consistent with app primary button styling

## localStorage Interaction

- Shared view MUST NOT read from or write to localStorage
- Existing localStorage data is preserved but ignored during shared view
- Navigating to the main app via CTA uses normal localStorage-based state
93 changes: 93 additions & 0 deletions specs/008-shareable-map-links/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Data Model: Shareable Map Links

**Date**: 2026-03-12 | **Branch**: `008-shareable-map-links`

## Entities

### QuestionIndex

A deterministic mapping from every question in the question bank to a stable integer index. Used for compact binary encoding in tokens.

| Field | Type | Description |
|-|-|-|
| version | uint8 | Index version — increments when questions are added/removed |
| entries | Map\<string, number\> | question_id → integer index |
| reverseEntries | Map\<number, string\> | integer index → question_id |

**Construction rule**: Sort all questions across all domains by `(domain_ids[0], id)` alphabetically. Assign index 0, 1, 2, ... in sort order.

**Invariants**:
- Every question has exactly one index
- Index assignment is deterministic (same question bank → same indices)
- Indices are contiguous (0 to N-1 for N questions)

### ResponseToken

A versioned, compressed, URL-safe encoding of a user's quiz responses.

| Field | Type | Description |
|-|-|-|
| version | uint8 | Token format version (currently 1) |
| count | uint16 | Number of encoded responses |
| entries | Array\<{index: uint16, value: int8}\> | Sparse response pairs |

**Value encoding**:

| Response State | Value |
|-|-|
| Unanswered | 0 (not stored — sparse) |
| Skipped | 1 |
| Correct | 2 |
| Incorrect | -1 (0xFF as uint8) |

**Binary layout**: `[version:1][count:2][index:2,value:1]×count`
- Total bytes: `3 + (count × 3)`
- Big-endian for multi-byte integers

**Lifecycle**:
1. **Created** when user clicks "Copy Link" in share modal
2. **Compressed** via pako deflate
3. **Encoded** to base64url string
4. **Appended** to URL as `?t=` parameter
5. **Decoded** when recipient opens the URL
6. **Inflated** via pako inflate
7. **Mapped** back to response objects using QuestionIndex reverse lookup

### SyntheticResponse

A minimal response object reconstructed from a decoded token. Compatible with the existing `$responses` store format for rendering purposes.

| Field | Type | Description |
|-|-|-|
| question_id | string | From QuestionIndex reverse lookup |
| is_correct | boolean | true if value === 2 |
| is_skipped | boolean | true if value === 1 |
| x | number | From question data (looked up by question_id) |
| y | number | From question data (looked up by question_id) |

**Note**: SyntheticResponses are NOT written to localStorage. They exist only in memory for the shared view session.

## Relationships

```text
QuestionIndex ──builds──> ResponseToken (encoding)
ResponseToken ──decodes──> SyntheticResponse[] (decoding)
SyntheticResponse ──feeds──> GP Estimator ──renders──> Heatmap
```

## State Transitions

```text
[User answers questions]
$responses (localStorage) ──encode──> ResponseToken ──compress──> base64url ──> URL

[Recipient opens URL]
URL ──parse ?t=──> base64url ──inflate──> ResponseToken ──decode──> SyntheticResponse[]
[Shared view renders map with SyntheticResponses]
```
Loading