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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Documentation update — 2026-09-06

- Add four actual-app screenshots with labelled demo data, capture details and exact-byte publication checks.
- Replace the README overview with a feature table and clarify local tutoring, storage, pack revisions and release limits.

## 0.2.0 — 2026-09-06

- Add an optional engineering pack: 31 lessons, six tracks and 96 exercise/material files. The original starter pack and its record identity stay unchanged.
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Contribute a bounded improvement that helps a learner begin, understand, practis
5. Check changed flows at desktop and narrow widths, with keyboard and reduced motion. Use `?qa` so test records stay separate.
6. Review files, Git history and the static asset list. Use the GitHub noreply address shown in your account's email settings. Keep secrets, personal exports, local settings, logs and screenshots of real records out of commits.

For README images, follow the [screenshot capture and review procedure](docs/screenshots/README.md). Only reviewed demo captures belong in documentation.

Keep the three-lesson starter stable unless a deliberate content revision is intended. New engineering material belongs in its own pack revision. A pack fingerprint selects a separate notebook; document that consequence instead of silently migrating progress.

AI connection changes need explicit local opt-in, a provider with no executable tools, no browser credentials, bounded requests, failure recovery and independent review. Do not add a public proxy or shared API key as a convenience. GitHub Actions uses synthetic tests and must never require a contributor's Codex auth.
Expand Down
108 changes: 63 additions & 45 deletions README.md

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions docs/screenshots/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Application screenshots

These are unedited browser captures of Padipps 0.2.0, app revision `5b45173988a447e082a812a02e7a0257af0d47fa`, taken on 6 September 2026. They show the public application running on a separate local port with chat disabled. The browser's default viewport was 1280 × 720; no device emulation or screenshot compositing was used.

| Image | Visible state |
| --- | --- |
| [Practice library](practice.jpg) | Engineering pack selected; 31 lessons, six overlapping track filters; Ink theme. |
| [Notebook](notebook.jpg) | One sample note titled “Demo note: plan the next study block”, marked Revisit; zero recorded attempts; Violet theme. |
| [Focus room](focus.jpg) | Short break ready to start following a five-second QA timer; Sand theme. |
| [Study shelf](shelf.jpg) | One book labelled “QA timer check”, its five-second duration and a source-linked editorial reflection; Sand theme. |

The notebook entry and timer receipt are synthetic demonstration data created through the interface in a `?qa` session. They are not personal notes, study evidence or claims of learning. No model conversation, account details, private connections or credentials were used in these captures. The book illustration is part of the shipped app; its provenance is in [third-party notices](../../THIRD_PARTY_LICENSES.md).

## Updating the images

Use the public app on a disposable port with `?qa`. Interact through the visible interface and label sample records. Capture the page itself, excluding desktop windows, account menus and browser chrome. Keep screenshots faithful to the shipped UI; do not generate a mockup or fill in model answers.

Before committing, inspect every image for private text and check metadata. These JPEGs contain a standard JFIF header and image segments, with no EXIF, XMP, ICC or comment segments. Their exact SHA-256 hashes are recorded in [reviewed-images.json](reviewed-images.json). The publication guard admits only matching bytes at the recorded screenshot paths. A changed image needs another manual review and a new hash; retain reviewed older hashes when they remain in Git history. Hash approval records the review, but cannot inspect pixels or prove privacy by itself.

Run the publication audit and verify that the images render in GitHub's README. Screenshots are repository documentation and are excluded from the static app build.
Binary file added docs/screenshots/focus.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/notebook.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/practice.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions docs/screenshots/reviewed-images.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"docs/screenshots/focus.jpg": [
"6a16d0e11e209864295206198531240d6af1b709f402a1189a51aac88cb78a54"
],
"docs/screenshots/notebook.jpg": [
"c5a88b859fda3329f4de8ae375245cce6eea35886b86278a6c4c471caf415343"
],
"docs/screenshots/practice.jpg": [
"8a8a32fef0bd64ef1ea5465840448b74cf49c65ef2bd6ad6d443c7ea4d45efe5"
],
"docs/screenshots/shelf.jpg": [
"ed8c5884835b2af060282f7b9d0b5b1fe5eda474be4f517f1d4d9b0bdc6a692f"
]
}
Binary file added docs/screenshots/shelf.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions scripts/audit-publication.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** Redacted pre-publication checks. These are guardrails, not a secret-free certification. */
import {execFileSync} from 'node:child_process';
import {createHash} from 'node:crypto';
import {readFile, lstat} from 'node:fs/promises';
import {fileURLToPath} from 'node:url';
import path from 'node:path';
Expand Down Expand Up @@ -37,6 +38,14 @@ export function publicCommitEmail(email, publicContactEmails=[]){
|| publicContactEmails.includes(email);
}

// Hashes record a manual pixel and metadata review, not an automated privacy guarantee.
export function reviewedScreenshot(file, buffer, manifest={}){
return /^docs\/screenshots\/[a-z0-9-]+\.jpg$/.test(file)
&& buffer[0]===0xff && buffer[1]===0xd8 && buffer[2]===0xff
&& Array.isArray(manifest[file])
&& manifest[file].includes(createHash('sha256').update(buffer).digest('hex'));
}

function publicContacts(policy){
if(!policy||Object.keys(policy).length!==1||!Array.isArray(policy.publicContactEmails)
||policy.publicContactEmails.some(email=>typeof email!=='string'||!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)))
Expand All @@ -46,10 +55,12 @@ function publicContacts(policy){

async function main(){
const publicContactEmails=publicContacts(JSON.parse(await readFile(path.join(root,'publication-policy.json'),'utf8')));
const screenshots=JSON.parse(await readFile(path.join(root,'docs/screenshots/reviewed-images.json'),'utf8'));
const findings=[],seen=new Set();let files=0,blobs=0,commits=0;
const inspect=(file,buffer,revision)=>{
files++;
for(const item of inspectPath(file))findings.push({file,...item,...(revision?{revision}:{})});
if(reviewedScreenshot(file,buffer,screenshots))return;
if(buffer.includes(0)){
if(!file.endsWith('.webp'))findings.push({file,rule:'unreviewed-binary',line:0});
else if(['EXIF','XMP ','ICCP'].some(tag=>buffer.includes(Buffer.from(tag))))findings.push({file,rule:'image-metadata-needs-review',line:0});
Expand Down
17 changes: 16 additions & 1 deletion tests/publication.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import test from 'node:test';
import {readFileSync} from 'node:fs';
import assert from 'node:assert/strict';
import {inspectText,inspectPath,publicCommitEmail} from '../scripts/audit-publication.mjs';
import {inspectText,inspectPath,publicCommitEmail,reviewedScreenshot} from '../scripts/audit-publication.mjs';
import {BROWSER_ASSETS,STATIC_FILES} from '../public-assets.mjs';

test('publication guard detects secrets without returning their values',()=>{
Expand All @@ -27,3 +28,17 @@ test('publication boundary excludes local state, auth, archives and backend from
assert.ok(BROWSER_ASSETS.includes('packs/engineering.json'));assert.ok(BROWSER_ASSETS.includes('chat-ui.js'));
for(const file of STATIC_FILES){assert.equal(inspectPath(file).length,0,file);assert.equal(file.endsWith('.mjs'),false,file);assert.equal(file.startsWith('tests/'),false,file);assert.equal(file.includes('..'),false,file);}
});

test('only exact manually reviewed screenshot bytes and paths are admitted',()=>{
const manifest=JSON.parse(readFileSync(new URL('../docs/screenshots/reviewed-images.json',import.meta.url)));
for(const file of Object.keys(manifest)){
const bytes=readFileSync(new URL('../'+file,import.meta.url));
assert.equal(reviewedScreenshot(file,bytes,manifest),true,file);
const changed=Buffer.from(bytes);changed[changed.length-1]^=1;
assert.equal(reviewedScreenshot(file,changed,manifest),false);
assert.equal(reviewedScreenshot(file,Buffer.concat([bytes,Buffer.from('private metadata')]),manifest),false);
assert.equal(reviewedScreenshot('assets/renamed.jpg',bytes,manifest),false);
assert.equal(reviewedScreenshot(file,bytes,{}),false);
assert.equal(reviewedScreenshot('docs/screenshots/../private.jpg',bytes,manifest),false);
}
});
Loading