Skip to content

Commit f7cf3a5

Browse files
Merge pull request #60 from ContextLab/009-fix-mobile-mode
Add shareable map links, tutorial fixes, and mobile mode spec
2 parents 3e5945a + 0d1df48 commit f7cf3a5

26 files changed

Lines changed: 1965 additions & 33 deletions

index.html

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,22 @@
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
6-
<meta name="description" content="Wikipedia Knowledge Map - An interactive visualization of 250,000 Wikipedia articles, their semantic relationships, and difficulty levels.">
6+
<meta name="description" content="An interactive tool that maps out everything you know. Answer questions and watch your personalized knowledge map take shape.">
77
<title>Knowledge Mapper</title>
88

9+
<!-- Open Graph meta tags -->
10+
<meta property="og:title" content="Knowledge Mapper">
11+
<meta property="og:description" content="An interactive tool that maps out everything you know. Answer questions and watch your personalized knowledge map take shape.">
12+
<meta property="og:image" content="https://context-lab.com/mapper/og-preview.png">
13+
<meta property="og:url" content="https://context-lab.com/mapper/">
14+
<meta property="og:type" content="website">
15+
16+
<!-- Twitter Card meta tags -->
17+
<meta name="twitter:card" content="summary_large_image">
18+
<meta name="twitter:title" content="Knowledge Mapper">
19+
<meta name="twitter:description" content="An interactive tool that maps out everything you know. Answer questions and watch your personalized knowledge map take shape.">
20+
<meta name="twitter:image" content="https://context-lab.com/mapper/og-preview.png">
21+
922
<!-- KaTeX for LaTeX rendering -->
1023
<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;">
1124
<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>

package-lock.json

Lines changed: 17 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"dependencies": {
2424
"@nanostores/persistent": "^1.3.3",
2525
"deck.gl": "^9.2.7",
26-
"nanostores": "^1.1.0"
26+
"nanostores": "^1.1.0",
27+
"pako": "^2.1.0"
2728
},
2829
"devDependencies": {
2930
"@playwright/test": "^1.58.2",

public/og-preview.png

409 KB
Loading
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Requirements Quality Checklist: Shareable Map Links
2+
3+
**Purpose**: Comprehensive requirements quality validation — PR review gate after implementation
4+
**Created**: 2026-03-12
5+
**Feature**: [spec.md](../spec.md)
6+
**Depth**: Standard (~30 items)
7+
**Audience**: PR reviewer
8+
9+
## Requirement Completeness
10+
11+
- [ ] CHK001 - Are encoding value semantics defined for all possible response states (correct, incorrect, skipped, unanswered)? [Completeness, Spec §FR-002]
12+
- [ ] CHK002 - Is the binary wire format fully specified with byte offsets, endianness, and field sizes? [Completeness, Contract §token-format]
13+
- [ ] CHK003 - Are requirements for the "Copy Link" button placement and styling within the share modal specified? [Gap, Spec §FR-009]
14+
- [ ] CHK004 - Is the CTA button's position, styling, and responsive behavior in the shared view defined? [Gap, Contract §url-contract]
15+
- [ ] CHK005 - Are requirements specified for all 5 social platform share buttons (LinkedIn, X, Bluesky, Facebook, Instagram)? [Completeness, Spec §FR-015]
16+
- [ ] CHK006 - Are loading/progress state requirements defined for the shared view while the GP estimator runs? [Gap]
17+
18+
## Requirement Clarity
19+
20+
- [ ] CHK007 - Is "minimal chrome" quantified with an explicit list of hidden vs. visible UI elements? [Clarity, Spec §FR-006]
21+
- [ ] CHK008 - Is "visually identical" (SC-005) defined with measurable criteria (pixel diff threshold, screenshot comparison method)? [Ambiguity, Spec §SC-005]
22+
- [ ] CHK009 - Is the "stable, deterministic index" sort order precisely defined (sort key, tie-breaking, collation)? [Clarity, Spec §FR-001]
23+
- [ ] CHK010 - Is "gracefully handle" for invalid tokens defined with specific fallback behavior? [Clarity, Spec §FR-011]
24+
- [ ] 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]
25+
- [ ] CHK012 - Is the Instagram "prompt to paste" UX described with specific wording, duration, and dismissal behavior? [Clarity, Spec §FR-020]
26+
27+
## Requirement Consistency
28+
29+
- [ ] CHK013 - Does the "read-only" requirement in US2 align with the "minimal chrome" clarification listing hidden elements? [Consistency, Spec §US2 vs §FR-006]
30+
- [ ] 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]
31+
- [ ] CHK015 - Is the terminology "token" used consistently across spec, plan, contracts, and tasks (not mixed with "hash", "code", "key")? [Consistency]
32+
- [ ] 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]
33+
34+
## Acceptance Criteria Quality
35+
36+
- [ ] 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]
37+
- [ ] 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]
38+
- [ ] CHK019 - Can SC-004 ("tokens remain decodable after question bank updates") be verified with a defined test procedure? [Measurability, Spec §SC-004]
39+
- [ ] CHK020 - Can SC-007 ("link previews display correct title, description, and preview image") be verified — are "correct" values specified? [Measurability, Spec §SC-007]
40+
41+
## Scenario Coverage
42+
43+
- [ ] CHK021 - Are requirements defined for the zero-response state in shared view (user shares before answering any questions)? [Coverage, Edge Case]
44+
- [ ] CHK022 - Are requirements specified for what happens when a shared URL is opened on an unsupported/old browser? [Coverage, Gap]
45+
- [ ] CHK023 - Are requirements defined for the shared view when the "all" domain bundle fails to load (network error)? [Coverage, Exception Flow]
46+
- [ ] CHK024 - Are requirements specified for concurrent sharing scenarios (user generates link, answers more questions, then recipient opens the old link)? [Coverage, Alternate Flow]
47+
48+
## Edge Case Coverage
49+
50+
- [ ] 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]
51+
- [ ] CHK026 - Are requirements defined for token URLs that pass through URL shorteners (bit.ly, t.co) — does the token survive? [Edge Case, Gap]
52+
- [ ] CHK027 - Is behavior specified for extremely long tokens (all 2500 questions answered) on platforms with URL length limits? [Edge Case, Spec §Edge Cases]
53+
- [ ] CHK028 - Are requirements defined for what happens if pako (compression library) fails to load or is unavailable? [Edge Case, Gap]
54+
55+
## Non-Functional Requirements
56+
57+
- [ ] CHK029 - Are accessibility requirements specified for the shared view CTA button (keyboard focus, screen reader label, contrast)? [Gap, Accessibility]
58+
- [ ] CHK030 - Are performance requirements defined for token encoding/decoding operations (max latency on mid-range hardware)? [Gap, Performance]
59+
- [ ] CHK031 - Are privacy considerations documented — do tokens reveal any personally identifiable information? [Gap, Privacy]
60+
- [ ] CHK032 - Are WCAG AA color contrast requirements specified for the CTA button and shared view elements? [Gap, Accessibility]
61+
62+
## Dependencies & Assumptions
63+
64+
- [ ] CHK033 - Is the assumption "social platforms support URLs up to 2000 characters" validated for all 5 target platforms? [Assumption, Spec §Assumptions]
65+
- [ ] CHK034 - Is the pako dependency version-pinned, and are fallback requirements specified if the CDN or npm package is unavailable? [Dependency, Gap]
66+
- [ ] CHK035 - Is the assumption "GitHub Pages serves static OG meta tags correctly to social crawlers" validated? [Assumption, Spec §Assumptions]
67+
68+
## Notes
69+
70+
- This checklist validates the REQUIREMENTS quality, not the implementation
71+
- Items marked [Gap] indicate missing requirements that should be added before final PR approval
72+
- Items marked [Ambiguity] indicate requirements that need sharper definition
73+
- Each item should be resolved by updating spec.md, not by verbal agreement
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Specification Quality Checklist: Shareable Map Links
2+
3+
**Purpose**: Validate specification completeness and quality before proceeding to planning
4+
**Created**: 2026-03-12
5+
**Feature**: [spec.md](../spec.md)
6+
7+
## Content Quality
8+
9+
- [X] No implementation details (languages, frameworks, APIs)
10+
- [X] Focused on user value and business needs
11+
- [X] Written for non-technical stakeholders
12+
- [X] All mandatory sections completed
13+
14+
## Requirement Completeness
15+
16+
- [X] No [NEEDS CLARIFICATION] markers remain
17+
- [X] Requirements are testable and unambiguous
18+
- [X] Success criteria are measurable
19+
- [X] Success criteria are technology-agnostic (no implementation details)
20+
- [X] All acceptance scenarios are defined
21+
- [X] Edge cases are identified
22+
- [X] Scope is clearly bounded
23+
- [X] Dependencies and assumptions identified
24+
25+
## Feature Readiness
26+
27+
- [X] All functional requirements have clear acceptance criteria
28+
- [X] User scenarios cover primary flows
29+
- [X] Feature meets measurable outcomes defined in Success Criteria
30+
- [X] No implementation details leak into specification
31+
32+
## Notes
33+
34+
- 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)
35+
- FR-013 explicitly prevents the "separate load page" drift risk raised in the issue comments
36+
- Compression/encoding approach mentioned in Assumptions section is descriptive context, not prescriptive implementation detail
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Contract: Response Token Binary Format
2+
3+
**Version**: 1 | **Date**: 2026-03-12
4+
5+
## Wire Format
6+
7+
```text
8+
Byte 0: version (uint8) — currently 0x01
9+
Bytes 1-2: count (uint16 big-endian) — number of response entries
10+
Bytes 3+: entries, each 3 bytes:
11+
[0-1] index (uint16 big-endian) — question index
12+
[2] value (int8) — response value
13+
```
14+
15+
## Value Encoding
16+
17+
| Response | Encoded Value | Byte Representation |
18+
|-|-|-|
19+
| Correct | 2 | 0x02 |
20+
| Skipped | 1 | 0x01 |
21+
| Incorrect | -1 | 0xFF |
22+
| Unanswered | 0 | (not stored) |
23+
24+
## Compression
25+
26+
1. Serialize to binary using the wire format above
27+
2. Compress with raw DEFLATE (pako `deflate` with `raw: true`)
28+
3. Encode compressed bytes as base64url (RFC 4648 §5): `+``-`, `/``_`, strip `=` padding
29+
30+
## URL Format
31+
32+
```text
33+
https://context-lab.com/mapper/?t={base64url_token}
34+
```
35+
36+
## Size Guarantees
37+
38+
| Answered Questions | Raw Bytes | Compressed (est.) | Base64url Chars | Total URL Length |
39+
|-|-|-|-|-|
40+
| 50 | 153 | ~100 | ~134 | ~175 |
41+
| 100 | 303 | ~200 | ~268 | ~309 |
42+
| 200 | 603 | ~380 | ~508 | ~549 |
43+
| 500 | 1503 | ~800 | ~1068 | ~1109 |
44+
| 2500 (all) | 7503 | ~3000 | ~4000 | ~4041 |
45+
46+
Base URL (`https://context-lab.com/mapper/?t=`) = 41 characters.
47+
48+
## Decoding Rules
49+
50+
1. Extract `t` parameter from URL query string
51+
2. Restore base64url: `-``+`, `_``/`, re-pad with `=` to multiple of 4
52+
3. Decode base64 to bytes
53+
4. Inflate with pako (`inflate` with `raw: true`)
54+
5. Parse version byte — if unsupported version, abort (fall back to normal app)
55+
6. Parse count (bytes 1-2, big-endian)
56+
7. For each entry: read index (uint16 BE), value (int8)
57+
8. Look up question_id from QuestionIndex using the token's version
58+
9. Skip entries whose index has no matching question (question was removed)
59+
60+
## Versioning
61+
62+
- Version byte `0x01`: Initial format as described above
63+
- Future versions may change field sizes or add metadata
64+
- Decoders MUST check the version byte and reject unknown versions gracefully
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Contract: URL Parameter Interface
2+
3+
**Version**: 1 | **Date**: 2026-03-12
4+
5+
## Query Parameters
6+
7+
| Parameter | Type | Required | Description |
8+
|-|-|-|-|
9+
| `t` | string (base64url) | No | Encoded response token. When present, app boots in shared view mode. |
10+
11+
## Behavior Matrix
12+
13+
| URL | Behavior |
14+
|-|-|
15+
| `/mapper/` | Normal app — landing screen, full UI |
16+
| `/mapper/?t={valid_token}` | Shared view — minimal chrome, read-only map |
17+
| `/mapper/?t=` | Normal app (empty token treated as absent) |
18+
| `/mapper/?t={invalid}` | Normal app (decode failure → silent fallback) |
19+
| `/mapper/?t={valid}&other=param` | Shared view (extra params ignored) |
20+
21+
## Shared View Mode
22+
23+
When a valid `?t=` token is detected:
24+
25+
1. **Skip** landing/welcome screen
26+
2. **Load** "All (general)" domain bundle
27+
3. **Decode** token into SyntheticResponse array
28+
4. **Run** GP estimator with SyntheticResponses
29+
5. **Render** map with heatmap + response dots
30+
6. **Show** minimal chrome: map canvas + "Map your *own* knowledge!" CTA button
31+
7. **Hide** header toolbar, quiz panel, video panel, minimap, drawer pulls
32+
33+
## CTA Button
34+
35+
- Text: "Map your *own* knowledge!"
36+
- Action: Navigate to `/mapper/` (no token — starts fresh normal session)
37+
- Position: Fixed bottom-center, visually prominent
38+
- Style: Consistent with app primary button styling
39+
40+
## localStorage Interaction
41+
42+
- Shared view MUST NOT read from or write to localStorage
43+
- Existing localStorage data is preserved but ignored during shared view
44+
- Navigating to the main app via CTA uses normal localStorage-based state
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Data Model: Shareable Map Links
2+
3+
**Date**: 2026-03-12 | **Branch**: `008-shareable-map-links`
4+
5+
## Entities
6+
7+
### QuestionIndex
8+
9+
A deterministic mapping from every question in the question bank to a stable integer index. Used for compact binary encoding in tokens.
10+
11+
| Field | Type | Description |
12+
|-|-|-|
13+
| version | uint8 | Index version — increments when questions are added/removed |
14+
| entries | Map\<string, number\> | question_id → integer index |
15+
| reverseEntries | Map\<number, string\> | integer index → question_id |
16+
17+
**Construction rule**: Sort all questions across all domains by `(domain_ids[0], id)` alphabetically. Assign index 0, 1, 2, ... in sort order.
18+
19+
**Invariants**:
20+
- Every question has exactly one index
21+
- Index assignment is deterministic (same question bank → same indices)
22+
- Indices are contiguous (0 to N-1 for N questions)
23+
24+
### ResponseToken
25+
26+
A versioned, compressed, URL-safe encoding of a user's quiz responses.
27+
28+
| Field | Type | Description |
29+
|-|-|-|
30+
| version | uint8 | Token format version (currently 1) |
31+
| count | uint16 | Number of encoded responses |
32+
| entries | Array\<{index: uint16, value: int8}\> | Sparse response pairs |
33+
34+
**Value encoding**:
35+
36+
| Response State | Value |
37+
|-|-|
38+
| Unanswered | 0 (not stored — sparse) |
39+
| Skipped | 1 |
40+
| Correct | 2 |
41+
| Incorrect | -1 (0xFF as uint8) |
42+
43+
**Binary layout**: `[version:1][count:2][index:2,value:1]×count`
44+
- Total bytes: `3 + (count × 3)`
45+
- Big-endian for multi-byte integers
46+
47+
**Lifecycle**:
48+
1. **Created** when user clicks "Copy Link" in share modal
49+
2. **Compressed** via pako deflate
50+
3. **Encoded** to base64url string
51+
4. **Appended** to URL as `?t=` parameter
52+
5. **Decoded** when recipient opens the URL
53+
6. **Inflated** via pako inflate
54+
7. **Mapped** back to response objects using QuestionIndex reverse lookup
55+
56+
### SyntheticResponse
57+
58+
A minimal response object reconstructed from a decoded token. Compatible with the existing `$responses` store format for rendering purposes.
59+
60+
| Field | Type | Description |
61+
|-|-|-|
62+
| question_id | string | From QuestionIndex reverse lookup |
63+
| is_correct | boolean | true if value === 2 |
64+
| is_skipped | boolean | true if value === 1 |
65+
| x | number | From question data (looked up by question_id) |
66+
| y | number | From question data (looked up by question_id) |
67+
68+
**Note**: SyntheticResponses are NOT written to localStorage. They exist only in memory for the shared view session.
69+
70+
## Relationships
71+
72+
```text
73+
QuestionIndex ──builds──> ResponseToken (encoding)
74+
ResponseToken ──decodes──> SyntheticResponse[] (decoding)
75+
SyntheticResponse ──feeds──> GP Estimator ──renders──> Heatmap
76+
```
77+
78+
## State Transitions
79+
80+
```text
81+
[User answers questions]
82+
83+
84+
$responses (localStorage) ──encode──> ResponseToken ──compress──> base64url ──> URL
85+
86+
[Recipient opens URL]
87+
88+
89+
URL ──parse ?t=──> base64url ──inflate──> ResponseToken ──decode──> SyntheticResponse[]
90+
91+
92+
[Shared view renders map with SyntheticResponses]
93+
```

0 commit comments

Comments
 (0)