A political conference and discussion platform, in Rust and Dioxus
wiki docs issue-notes.md
4.9 kB
Markdown
at main

Notes on the open issues #

Written 2026-07-31, after working through the small ones. Two of them turned out to need a decision rather than an implementation, and one turned out to be already done. This is what was found, so the decisions can be made from evidence rather than from memory.

Already delivered: #11 (random ballot order), #7 in part #

#11 is implemented in the app deployed on 2026-07-30 and was filed against the React app that radikal.wiki served until that evening. ballot_order() shuffles the options with Blank pinned last, memoised per poll and option count so it cannot reshuffle under a voter mid-ballot. Both branches of the ballot — single-choice radios and multi-choice checkboxes — iterate the shuffled order while selecting by the ORIGINAL index, so the stored vote is unaffected by what the screen showed. Covered by ballot_order_keeps_blank_last_and_is_a_permutation.

#7 was half delivered for the same reason: documents autolinked, comments and plain text nodes did not. They do now.

Done: images on a comment #

Straightforward in the end, once the mechanism was read rather than inferred. The first pass here concluded it was blocked, on the grounds that a file is only readable through the nodes.file_id column and no client may write it. Both halves of that are true and the conclusion was still wrong: file_id is a GENERATED column, derived from data->>'image' (or data->>'fileId') and validated as a uuid. The client never writes it. It writes data.image, exactly as a candidate's photo and a document's cover image already do, and the column — and with it the storage permission — follows.

So a comment carries one image, stored at data.image. One, because the generated column holds one uuid; a second would be invisible, which is worse than not offering it.

Verified against production with the shape the app now inserts: a member posted a comment with data.image, the file_id column generated itself, and a DIFFERENT member of that context could read the file row. The probe was removed afterwards.

What is worth knowing about the permission, since it is not obvious:

  • storage.files is readable when the file was uploaded by the reader (uploaded_by_user_id) or when a NODE the reader may read points at it.
  • uploaded_by_user_id is null on all 1023 files in this deployment. That branch has never matched anyone, so the node reference is the only path — which is why storing an id anywhere other than where the generated column reads it produces an image nobody can see, not even its author.

Needs design: #10, node revision table #

Not started deliberately. The schema is the easy part; the questions are:

  • What is a revision? Every autosave (the editor debounces at a few seconds, so a long edit is hundreds), every explicit save, or every SUBMIT? The last is the one with meaning in this wiki: a policy becomes immutable when submitted, and the interesting question is what changed between two submitted versions.
  • What is kept? A full data snapshot per revision is simple and, at the size of these documents, cheap — thousands of nodes at a few KB each. A diff is smaller and much harder to render.
  • Who sees them? Probably anyone who can read the node. A revision can contain text an author later removed on purpose, which is an argument for owner-only, and against keeping them for comments at all.
  • How long? Forever is a decision, not a default.

My suggestion: node_revisions (id, node_id, data, name, created_at, author_id), written by a trigger on nodes when a SUBMITTED node's data or name changes, content mimes only, readable by whoever can read the node. That is one migration and a history view, and it deliberately does not record drafts.

Needs design: #4, open contexts #

"Contexts where anyone can join." The permission model already has the pieces — permissions rows keyed by context, mime, role and parent mime, plus the members table with accepted — so an open context is mostly a policy question:

  • Does joining make you a member immediately, or a member with accepted = false that an owner confirms? The second is the current invitation shape and needs no new state.
  • What can a self-joined member do? The permissions rows already say what a member may insert, update and delete per mime; an open context probably wants a NARROWER set than an invited one (comment and vote, not create).
  • Can they leave, and does leaving remove their votes? It must not.
  • Is the context discoverable before joining? Today a context you are not in is invisible unless it is public.

This one is worth a conversation before code: it is the first feature that lets someone into a context without an owner acting, which is a security boundary rather than a feature flag. Everything else in this file is reversible; this is not.