Skip to content

Share links: the compact ?p= form (raw deflate, base64url); «Zum Lesen» - #40

Merged
cedricvogel-leo merged 1 commit into
mainfrom
feature/compact-links
Nov 6, 2026
Merged

cedricvogel-leo merged 1 commit into
mainfrom
feature/compact-links

Conversation

@cedricvogel-leo

@cedricvogel-leo cedricvogel-leo commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

P46: shorter share links, and a drawer heading for files to read.

1. The compact share link ?p=

What changes: «Teilen» writes the same JSON payload as before, field for field: v, name, mainFile, openFile, readOnly and files in tab order. Only the encoding changes:

  • Compression: the payload's UTF-8 bytes, compressed with raw deflate. That's RFC 1951: no zlib or gzip header and no checksum.
  • Text form: base64url (RFC 4648 §5, alphabet A–Z a–z 0–9 - _), without = padding.
  • Where: ?p=, or #p= when path and query would exceed 8,192 characters.

Reading:

  • The IDE reads ?p= / #p= and the old ?project= / #project= (lz-string), with no end date.
  • A broken or cut p shows the usual «Dieser Link ist unvollständig oder beschädigt …».
  • A link's payload may inflate to at most 8 MB, so a short link can't freeze the page.

Why fflate rather than CompressionStream: CompressionStream is asynchronous. On an iPad, «Teilen» must write the clipboard inside the tap's own handler, so building the link has to be synchronous. That's why I used fflate 0.8.3 (MIT, synchronous, level 9). It writes the same format, and the old-form fallback isn't needed since fflate works in every browser. Any raw deflate stream reads, e.g. Node's zlib.deflateRawSync, whose bytes differ from fflate's for the same payload.

Numbers (the Etappe-4-shaped hand-in: aufgabe.md and parser.py by URL, auswerter.py with docstrings):

Link length
?project= (lz-string) 1,980
?p=, fflate (the IDE) 1,185 (−40 %)
?p=, zlib level 9 (e.g. a course script) 1,172

The 2,083 note is measured on the new link, as before.

Test vector (in the README too; test:browser checks it):

{"v":1,"name":"Test","mainFile":"main.py","files":{"main.py":{"path":"main.py","label":"main","kind":"python","code":"print(\"Grüezi\")\n"}}}

These exact bytes, as JSON.stringify writes them, go through zlib.deflateRawSync(Buffer.from(json, 'utf8'), { level: 9 }).toString('base64url'), which gives

VcxBCoMwFATQu8yqQhDc_gO0F3CZTVq_-DF-gwbBhtysOy9WAiK4m3kMk7CBGgN1E4PQ8hphMDnRp_giJdZhh0EvnldQuogSgovDbeTdm_0pMBhFOxDCHoe59M_cldOwiMaHxWs5fvwVi8oqcs5_

so https://glpython-edu.github.io/?p= + that opens main.py with print("Grüezi").

2. «Zum Lesen» in the drawer

Files loaded by URL are now grouped by kind:

  • Python files under Bibliotheken;
  • any other file, e.g. a task's aufgabe.md, under Zum Lesen (en: To read).

I chose the split over one neutral «Schreibgeschützt»: «Bibliotheken» says what a parser.py is for, and «Zum Lesen» says what to do with the instructions.

Checks (test:browser, all 21 pass)

New cases:

  • Round trip: a task link (library by URL, aufgabe.md by URL as openFile, tab order, an edit in auswerter.py), then «Teilen» and opening the result.
    • The link matches ?p=[A-Za-z0-9_-]+.
    • It opens on aufgabe.md, with the same tab order and the same code and readOnly for every file.
    • The test reads the link with Node's zlib, which checks that a course script can read the IDE's links.
  • Test vector and old links: zlib writes exactly the vector above, and the IDE opens it. Old ?project= and #project= links open as before.
  • Cut link: a ?p= link cut to 60 % shows the notice and an empty project.
  • Drawer: bib.py under «Bibliotheken», aufgabe.md under «Zum Lesen».

WebKit (Safari's engine), with an iPad profile (Playwright, not on a real iPad): a zlib-written ?p= link with umlauts and an emoji opens on its openFile. «Teilen» writes ?p=, which reads back identically.

Lint passes (only the existing warnings in coi-serviceworker.js), and so does the build. THIRD_PARTY_NOTICES.md lists fflate.

Going live

  1. Merge this first. It deploys the IDE, which then reads and writes ?p=.
  2. Then glpython-liascript#34 (the embed's IDE links in ?p=), and a course patch bump. If the macro went first, a course's ?p= link would open an empty project in the old IDE.
  3. Tabs left open: an IDE tab opened before the deploy offers «Neue Version verfügbar». A link always opens a fresh page anyway.

Manual tests

Desktop (Chrome, Safari, Firefox):

  1. In an old course link or an old hand-in link (?project=): opens as before.
  2. «Teilen»: the copied link starts with https://glpython-edu.github.io/?p= and is clearly shorter. Opening it in a new tab gives the same project and open tab.
  3. A link cut by hand (delete the last third): the notice appears.
  4. A task link with aufgabe.md by URL: the drawer shows «Zum Lesen» with the book icon and the lock.

iPad (Safari, external keyboard):

  1. «Teilen» copies right away. Paste it into Notes or OneNote, tap it, and the same project opens.
  2. A hand-in link pasted into OneNote: it isn't cut (under 2,083 characters).
  3. The drawer opens with the keyboard (Tab to 📁, Enter), and both headings are readable.

Follow-ups (not done)

  • Field removal (label, kind, path, readOnly on URLs) would save another 5–10 %. You asked me not to build it for now.
  • The 8 MB inflate limit rejects nothing a real project needs; it only guards against a crafted link.
  • Old IDE versions can't read ?p=. Only an IDE tab left open from before the deploy is affected, and it offers to reload.

🤖 Generated with Claude Code

«Teilen» writes the payload (unchanged, field for field) compressed with
raw deflate (fflate, level 9) in base64url without padding as ?p= (#p=
beyond 8,192 characters): about 40 % shorter, a hand-in of 1,980
characters becomes 1,185. ?project= links (lz-string) are read forever.
fflate is synchronous, so «Teilen» still copies within the tap (iPad).
The README documents the format with a zlib test vector, which
test:browser opens.

The drawer lists files by URL that aren't Python (a task's instructions)
under «Zum Lesen», Python files under «Bibliotheken».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cedricvogel-leo
cedricvogel-leo merged commit cb377bd into main Nov 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant