From aa53bf2978bd00735ec755589e0786481a476b6b Mon Sep 17 00:00:00 2001 From: Carlos Vivar Rios <45425937+caviri@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:45:28 +0200 Subject: [PATCH] docs(minimal-user-flows): atomic requirements. --- .../end-to-end-minimal-hackathon.jsonl | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/dev/minimal-user-flows/end-to-end-minimal-hackathon.jsonl diff --git a/docs/dev/minimal-user-flows/end-to-end-minimal-hackathon.jsonl b/docs/dev/minimal-user-flows/end-to-end-minimal-hackathon.jsonl new file mode 100644 index 00000000..5ceb003c --- /dev/null +++ b/docs/dev/minimal-user-flows/end-to-end-minimal-hackathon.jsonl @@ -0,0 +1,88 @@ +{"type":"pages-state","id":1,"at":"before anyone signs in","phase":"—","capabilities":[],"sees":{"anonymous":"the landing page and an empty public hackathon list; a “Log in” button","admin1":"not signed in","user1":"not signed in","user2":"not signed in"}} +{"type":"action","id":2,"actor":"admin1","title":"logs in to the platform","page":"/ → “Log in” → Keycloak sign-in","provides":"username, password","comment":"Signs in through Keycloak; the session is what identifies them and their role on every step below."} + +# Hackathon Creation +{"type":"action","id":3,"actor":"admin1","title":"admin creates and publishes the hackathon","page":"/hackathons/create — fill name, dates, visibility, main image","provides":"name, description rich markdown (tables, youtube embeded videos, images), visibility, main image, startsAt, endsAt","comment":"Creates the event and returns the hackathonId every later step targets. Logo is an image url."} +{"type":"action","id":5,"actor":"admin1","title":"admin confirms the event is published","page":"/my/hackathon/{id}/overview","provides":"hackathonId","comment":"Reads the event back to confirm name, description and image URL were stored."} +{"type":"pages-state","id":6,"at":"the event is created and public","phase":"none declared yet","capabilities":["(defaults — nothing scheduled yet)"],"sees":{"anonymous":"the public event page — name, description, logo","admin1":"the full manage hub for the new event (config, timeline, participants, teams, prizes)","user1":"(not registered) the public event page","user2":"(not registered) the public event page"}} + +# Hackathon Setup +{"type":"action","id":7,"actor":"admin1","title":"admin builds the custom registration form","page":"/my/hackathon/{id}/forms — Registration form (add questions)","provides":"Affiliation (text, required); Experience level (select, required); Skills (tags); Why do you want to take part? (textarea, required); Dietary requirements (text); T-shirt size (select); I accept the Code of Conduct (consent, required); I consent to event photography (consent)","comment":"The admin composes a CUSTOM registration questionnaire — each question carries a label, a type (short text, single-choice, tags, or long text) and whether it is required. Here: Affiliation (text, required), Experience level (single-choice), Skills (tags), a free-text Motivation (required), Dietary needs, and T-shirt size (single-choice), plus two consent checkboxes (Code of Conduct required, photos optional). Participants answer exactly these on their registration form."} +{"type":"action","id":8,"actor":"admin1","title":"admin builds the custom submission form","page":"/my/hackathon/{id}/forms — Submission form (add questions)","provides":"Repository URL (url, required); Live demo URL (url); One-paragraph summary (textarea, required); Primary category (select, required); Slides (PDF) — upload or link (file-or-url)","comment":"The admin defines the CUSTOM questionnaire teams answer when they turn work in: Repository URL (required), a Live demo URL, a Summary (long text, required), a Primary category (single-choice, required), and Slides (upload or external link). The submission is validated against exactly these questions."} +{"type":"action","id":9,"actor":"admin1","title":"admin sets the voting policy","page":"/my/hackathon/{id}/voting — Rules","provides":"hackathonId, mechanism, oneBallotPer, ownTeamVoting, organizerVoting, tieBreak","comment":"Sets the rules of the vote up front: mechanism, whether own-team votes count, and how ties break."} +{"type":"action","id":11,"actor":"admin1","title":"admin sets the email templates","page":"/my/hackathon/{id}/email","provides":"hackathonId, templates","comment":"The templates the platform sends at each milestone, written once with placeholders."} +{"type":"action","id":12,"actor":"admin1","title":"admin defines the prize table","page":"/my/hackathon/{id}/prizes","provides":"rank, title","comment":"Records the prize table; the admin has the final say on prizes."} +{"type":"action","id":13,"actor":"admin1","title":"admin sets the phases with time windows","page":"/my/hackathon/{id}/??PHASES??","provides":"hackathonId, registrationOpens, registrationCloses, proposalsClose, preferencesClose, submissionsClos","comment":"The clock: when each stage opens and closes. Opened now so registration and proposals can proceed."} +{"type":"action","id":16,"actor":"admin1","title":"admin adds the Ideation phase","page":"/my/hackathon/{id}/timeline → New phase","provides":"hackathonId, name, startsAt, endsAt, description","comment":"Lays the timeline: each phase is a named window participants see. Ideation is first."} +{"type":"action","id":17,"actor":"admin1","title":"admin adds the Hacking phase","page":"/my/hackathon/{id}/timeline → New phase","provides":"hackathonId, name, startsAt, endsAt, description","comment":"The build window."} +{"type":"action","id":18,"actor":"admin1","title":"admin adds the Judging phase","page":"/my/hackathon/{id}/timeline → New phase","provides":"hackathonId, name, startsAt, endsAt, description","comment":"The final window, where voting happens."} +{"type":"action","id":19,"actor":"admin1","title":"admin schedules voting to open in the Judging phase","page":"/my/hackathon/{id}/timeline/{phaseId}/edit — tick “Vote”","provides":"hackathonId, capability, openInPhaseId","comment":"Linking VOTE to the Judging phase means voting opens automatically when that phase becomes current — not before."} +{"type":"action","id":20,"actor":"admin1","title":"admin sets the opening capabilities","page":"/my/hackathon/{id}/manage — capability panel","provides":"hackathonId, capabilities","comment":"Register and propose open now; submit, vote and results stay closed until their phases."} +{"type":"action","id":21,"actor":"admin1","title":"admin declares the Ideation phase current","page":"/my/hackathon/{id}/manage — Now / Next","provides":"hackathonId, phaseId","comment":"Declares which phase is “now” so the hub and participants agree on the current stage."} +{"type":"pages-state","id":22,"at":"setup complete — registration open, Ideation is current","phase":"Ideation","capabilities":["register","propose_projects","set_team_preferences"],"sees":{"anonymous":"the public event page and the two pre-proposed projects","admin1":"everything, plus the Now/Next box showing Ideation","user1":"the public event page with a Join button (registration is open)","user2":"the public event page with a Join button"}} + +# Projects addition by admin +{"type":"action","id":23,"actor":"admin1","title":"admin proposes Project1 (preconfigured)","page":"/my/hackathon/{id}/projects/proposals/propose","provides":"hackathonId, trackId, title, description","comment":"The admin pre-proposes Project1 so the event ships with projects ready — participants prefer and join these rather than proposing their own."} +{"type":"action","id":24,"actor":"admin1","title":"admin approves Project1","page":"/my/hackathon/{id}/projects/proposals → Approve","provides":"projectId","comment":"Approves Project1, making it a real project a team can be built around."} +{"type":"action","id":25,"actor":"admin1","title":"admin proposes Project2 (preconfigured)","page":"/my/hackathon/{id}/projects/proposals/propose","provides":"hackathonId, trackId, title, description","comment":"The admin pre-proposes Project2, so there are two projects to spread the two users across."} +{"type":"action","id":26,"actor":"admin1","title":"admin approves Project2","page":"/my/hackathon/{id}/projects/proposals → Approve","provides":"projectId","comment":"Approves Project2."} + +# 1st User registration +{"type":"action","id":27,"actor":"user1","title":"logs in to the platform","page":"/ → “Log in” → Keycloak sign-in","provides":"username, password","comment":"Signs in through Keycloak; the session is what identifies them and their role on every step below."} +{"type":"pages-state","id":28,"at":"user1 signs in — a platform account now exists","who":"user1","page":"/manage/users","shows":"a new row in the platform-wide user list (admin only)"} +{"type":"action","id":29,"actor":"user1","title":"user1 registers","page":"/dashboard → Join","provides":"hackathonId","comment":"user1 registers; waitlisted until the organiser approves."} +{"type":"pages-state","id":30,"at":"user1 joins the hackathon","who":"user1","page":"/my/hackathon/{id}/participants","shows":"a new row badged “Waitlisted” in Manage participants (the registration list)"} +{"type":"action","id":31,"actor":"admin1","title":"admin approves user1","page":"/my/hackathon/{id}/participants → Approve","provides":"hackathonId, userId","comment":"The organiser confirms user1 — approval turns a registrant into a full participant."} +{"type":"pages-state","id":32,"at":"admin approves user1","who":"user1","page":"/my/hackathon/{id}/participants","shows":"user1's row flips from “Waitlisted” to “Confirmed”"} +{"type":"action","id":33,"actor":"user1","title":"user1 configures their profile (name, skills, avatar image URL)","page":"/account — edit profile","provides":"displayName, affiliation, skills, avatarUrl","comment":"user1 fills their platform profile. The avatar is set from an image URL hosted by an EXTERNAL provider — a link, not an upload; the platform stores only the URL."} +{"type":"action","id":34,"actor":"user1","title":"user1 submits the registration form (participation)","page":"/register/{id} — the registration form","provides":"hackathonId, responses, consents","comment":"user1 answers the admin's custom registration questions (affiliation, experience, skills, motivation, diet, T-shirt) and ticks the required consent; the answers are stored against their participation."} + +# 2nd User registration +{"type":"pages-state","id":35,"at":"user1 submits the registration form","who":"user1","page":"/my/hackathon/{id}/participants → View","shows":"their custom-form answers (affiliation, experience, motivation …) are now on file under their row"} +{"type":"action","id":36,"actor":"user2","title":"logs in to the platform","page":"/ → “Log in” → Keycloak sign-in","provides":"username, password","comment":"Signs in through Keycloak; the session is what identifies them and their role on every step below."} +{"type":"pages-state","id":37,"at":"user2 signs in — a platform account now exists","who":"user2","page":"/manage/users","shows":"a new row in the platform-wide user list (admin only)"} +{"type":"action","id":38,"actor":"user2","title":"user2 registers","page":"/dashboard → Join","provides":"hackathonId","comment":"user2 registers."} +{"type":"pages-state","id":39,"at":"user2 joins the hackathon","who":"user2","page":"/my/hackathon/{id}/participants","shows":"a second row badged “Waitlisted”"} +{"type":"action","id":40,"actor":"admin1","title":"admin approves user2","page":"/my/hackathon/{id}/participants → Approve","provides":"hackathonId, userId","comment":"The organiser confirms user2."} +{"type":"pages-state","id":41,"at":"admin approves user2","who":"user2","page":"/my/hackathon/{id}/participants","shows":"user2's row flips to “Confirmed”"} +{"type":"action","id":42,"actor":"user2","title":"user2 configures their profile (name, skills, avatar image URL)","page":"/account — edit profile","provides":"displayName, affiliation, skills, avatarUrl","comment":"user2 fills their profile and sets an avatar from an EXTERNAL image URL (a link to a picture hosted elsewhere)."} +{"type":"action","id":43,"actor":"user2","title":"user2 submits the registration form (participation)","page":"/register/{id} — the registration form","provides":"hackathonId, responses, consents","comment":"user2 answers the same custom questions with their own responses and submits their participation."} +{"type":"pages-state","id":44,"at":"both users are registered and confirmed participants","phase":"Ideation","capabilities":["register","propose_projects","set_team_preferences"],"sees":{"anonymous":"the public event page (never the member area)","admin1":"the participants roster with two confirmed people and their registration answers","user1":"the member area — overview, projects, teams, and their own profile; may set preferences","user2":"the member area; may set preferences"}} + +# Projects preferences +{"type":"action","id":45,"actor":"user1","title":"user1 prefers Project1","page":"/my/hackathon/{id}/projects — mark a preferred project","provides":"projectId","comment":"user1 marks the project they want to work on; organisers use preferences for team matching."} +{"type":"pages-state","id":46,"at":"user1 sets a project preference","who":"user1","page":"/my/hackathon/{id}/projects — preferences export","shows":"user1's preferred project shows in the organiser's preferences export"} +{"type":"action","id":47,"actor":"user2","title":"user2 prefers Project2","page":"/my/hackathon/{id}/projects — mark a preferred project","provides":"projectId","comment":"user2 marks their preferred project."} + +# Team formation +{"type":"action","id":48,"actor":"admin1","title":"admin exports preferences for team matching in csv/json","page":"/my/hackathon/{id}/projects — Export preferences","provides":"hackathonId","comment":"The organiser pulls everyone's preferences to inform how teams are formed."} +{"type":"action","id":49,"actor":"admin1","title":"admin creates Team1 on Project1","page":"/my/hackathon/{id}/teams/manage → New team","provides":"name, projectId","comment":"Team1 is bound to Project1 — a seat the CSV import will fill."} +{"type":"action","id":50,"actor":"admin1","title":"admin creates Team2 on Project2","page":"/my/hackathon/{id}/teams/manage → New team","provides":"name, projectId","comment":"Team2 is bound to Project2."} +{"type":"action","id":51,"actor":"admin1","title":"admin defines team membership from an uploaded CSV","page":"/my/hackathon/{id}/teams/manage → Import → upload CSV → Apply","provides":"upload teams.csv (columns: user_email, project, team)","comment":"The organiser assigns each confirmed participant to a team in bulk: user1 joins Team1, user2 joins Team2. Align the copy strings to the Manage-Teams importer when running for real."} +{"type":"pages-state","id":52,"at":"teams are formed","phase":"Ideation","capabilities":["set_team_preferences"],"sees":{"anonymous":"the public event page","admin1":"the teams board with Team1 and Team2 and their members","user1":"their team (Team1) and its page","user2":"their team (Team2)"}} +{"type":"pages-state","id":53,"at":"teams assigned from the CSV","who":"user1 & user2","page":"/my/hackathon/{id}/teams","shows":"user1 on Team1 and user2 on Team2 in the teams board / people panel"} + +# Hackathon phase +{"type":"action","id":54,"actor":"admin1","title":"admin advances to the Hacking phase","page":"/my/hackathon/{id}/manage — Now / Next → advance","provides":"hackathonId, phaseId","comment":"Moving the marker into Hacking opens the submissions the phase names."} + +# Projects submissions +{"type":"pages-state","id":55,"at":"Hacking phase — submissions open","phase":"Hacking","capabilities":["create_project_submissions"],"sees":{"anonymous":"the public event page","admin1":"submissions as they arrive","user1":"a “Turn in work” control for Team1","user2":"a “Turn in work” control for Team2"}} +{"type":"action","id":56,"actor":"user1","title":"user1 turns in Team1's work","page":"/my/hackathon/{id}/submissions","provides":"teamId, projectId, result, form","comment":"user1 submits Team1's work against Project1."} +{"type":"pages-state","id":57,"at":"user1 turns in Team1's work","who":"Team1","page":"/my/hackathon/{id}/submissions","shows":"Team1's submission appears for organisers to review"} +{"type":"action","id":58,"actor":"user2","title":"user2 turns in Team2's work","page":"/my/hackathon/{id}/submissions","provides":"teamId, projectId, result, form","comment":"user2 submits Team2's work."} + +# Voting phase +{"type":"action","id":59,"actor":"admin1","title":"admin advances to the Judging phase","page":"/my/hackathon/{id}/manage — Now / Next → advance","provides":"hackathonId, phaseId","comment":"Advancing to Judging opens the VOTE capability, which was linked to this phase in setup."} +{"type":"action","id":60,"actor":"admin1","title":"admin opens voting","page":"/my/hackathon/{id}/voting → Open voting","provides":"hackathonId, votingEnabled","comment":"Voting needs BOTH this master switch and the VOTE capability the Judging phase opened; the organiser flips it here."} +{"type":"pages-state","id":61,"at":"Judging phase — voting open","phase":"Judging","capabilities":["vote"],"sees":{"anonymous":"the public event page","admin1":"the tally as ballots arrive (organisers do not vote)","user1":"a ballot to cast (not for their own team)","user2":"a ballot to cast"}} +{"type":"action","id":62,"actor":"admin1","title":"admin creates a vote category","page":"/my/hackathon/{id}/voting → Add category","provides":"hackathonId, name, description, votingMethod, voterType","comment":"A category is what ballots are cast in; opening voting needs at least one."} +{"type":"action","id":63,"actor":"user1","title":"user1 casts a ballot (for Team2)","page":"/my/hackathon/{id}/voting → cast a ballot","provides":"singleChoice","comment":"user1 votes for the other team's submission — own-team voting is off in this event's policy."} +{"type":"pages-state","id":64,"at":"user1 casts a ballot","who":"user1","page":"/my/hackathon/{id}/voting — tally (organiser-only)","shows":"user1's ballot is counted in the tally; ballots are organiser-only to read"} +{"type":"action","id":65,"actor":"user2","title":"user2 casts a ballot (for Team1)","page":"/my/hackathon/{id}/voting → cast a ballot","provides":"singleChoice","comment":"user2 votes for the other team."} + +# Winners assignation +{"type":"action","id":66,"actor":"admin1","title":"admin records the winner","page":"/my/hackathon/{id}/voting → record placement","provides":"categoryId, submissionId, position, title","comment":"The admin places the winner from the tally — results are advisory until the admin records them (the admin has the final voice)."} +{"type":"action","id":67,"actor":"admin1","title":"admin publishes the results","page":"/my/hackathon/{id}/manage — capability panel (View results)","provides":"hackathonId, capabilities","comment":"Turning on VIEW_RESULTS is what publishes the placements to participants."} +{"type":"pages-state","id":68,"at":"results published","phase":"Judging","capabilities":["vote","view_results"],"sees":{"anonymous":"the public event page (and published winners, if surfaced there)","admin1":"the recorded placements","user1":"the published winner on the results page","user2":"the published winner"}} +{"type":"action","id":69,"actor":"user1","title":"user1 views the published results","page":"/my/hackathon/{id}/voting — Results","provides":"categoryId","comment":"user1 sees the published winner. The event is complete."} +{"type":"action","id":70,"actor":"user2","title":"user2 views the published results","page":"/my/hackathon/{id}/voting — Results","provides":"categoryId","comment":"user2 sees the published winner. The event is complete."}