Questify

2.21.0

Released

Features

  • With NEXT_PUBLIC_FEATURE_QUEST_COMMENTS on, the quest page has a Discussion thread below the tasks, and every task, on the quest page and on /tasks, has a Comments toggle showing how many comments it has. A thread loads its newest 20 comments when it is shown, and older ones on request with Show earlier comments. A thread that fails to load says so and offers a retry instead of an empty thread. Party members can post, and a URL in a comment opens in a new tab. Authors can edit their own comments, and the author or the quest owner can delete one after confirming. Edited comments are marked, comments from deleted accounts show as from a former member, and an archived quest shows that its comments are closed. (#575)

  • A task's investigations now sit behind a toggle, like its comments, collapsed by default and labelled with how many the task has. Opening one loads nothing — an investigation already arrives with the task — and a question asked from the card opens the section on its own, so the pending row and the answer that follows it are still watched as they land. The investigations and comments toggles are now two matching rows at the foot of the task card, on the quest page and on /tasks. (#646)

Fixes

  • The check-in, complete-quest and delete-quest dialogs now hold still while they are working. Each one showed a “Saving…”, “Completing…” or “Deleting…” label and disabled its buttons for the duration — except none of that ever happened, because the signal behind it was switched off the instant the request started. So the buttons stayed live, and clicking the background or pressing Escape closed the dialog out from under a save, a completion or a permanent delete that was still running, with the result left nowhere to appear. The pending state now lasts as long as the request does, and while it does the dialog cannot be dismissed by accident. (#597)

  • The check-in, complete-quest, delete-quest and location-consent dialogs are now bounded to the screen they are painted on. Each one used to grow with its content, and because the overlay behind it is fixed in place there was nothing to scroll — on a short viewport, or a phone held sideways, the dialog's own Cancel and confirm buttons ended up below the bottom of the screen with no way to reach them. A long quest title did it on the delete confirmation, a server error on the check-in note. The panel is now never taller than the screen, its body scrolls, and the buttons stay put at the foot of it — the same shape the feedback modal (#557) and the investigation prompt (#569) already use. The check-in error moved down beside the buttons, where it is visible from wherever Save was pressed. (#597)

  • Removed task reordering. PATCH /api/tasks/reorder is gone, along with the move up/down buttons, and sortOrder/order are no longer accepted when creating or updating a task. New tasks are always added at the end, and sort_order can no longer be changed through the database API either. For a party member the old route saved part of the new order and quietly skipped every task that wasn't theirs, which could leave two tasks in the same position. The "Manual order" sort is now "Order added". (#576)

  • The task manager now matches the new rule. A member can't edit or progress a task nobody is assigned to, gets no assignee picker (tasks they add are assigned to them), and "Complete all" only completes their own tasks instead of failing on everyone else's. The API explains a refusal with a 403 message. (#576)

Security

  • Party members can no longer update a quest directly. The Party members can update shared quests. policy let any member change every quest column except the owner through the Supabase API, including its visibility, deletion, status, title and context, although the app only ever writes quests as their owner. The policy was removed; completion and attendance still work because they run in database functions. (#575)

  • A Party Quest member can now only change a task that is assigned to them. Before, any member could retitle, finish or claim a task nobody was assigned to, and could take a task out of the owner's quest by moving it into a quest of their own through the database API, which worked like deleting it. Only the owner now assigns, reassigns or unassigns tasks, and a member can only add a task assigned to themselves. Nobody signed in can move a task to another quest. Owners are unaffected apart from that last point, and there was never a way to move a task in the app. (#576)

Technical changes· 6 entries for database, tooling, CI, tests and docs

Database

  • Added quest_comments for discussion on a quest and on each of its tasks. A comment is plain text, stored trimmed, 1–2000 characters, and never only whitespace. normalize_quest_comment_body() trims exactly the characters JavaScript's trim() removes, and both inserts and edits go through it. The owner and seated party members can read and post comments; public readers and anonymous visitors cannot, even on a public quest. A task comment is always filed under its task's quest, whatever quest id is sent. Only the author can edit a comment, through edit_quest_comment(). The author or the quest owner can soft-delete it through delete_quest_comment(), and a deleted comment is never returned. list_quest_comments() returns one thread at a time, newest first, in pages of at most 100 with a (created_at, id) cursor, with each author's name and what the caller may do to each comment. get_quest_comment_counts() returns how many comments each thread holds. An archived quest takes no new comments or edits, but deleting still works. A deleted account's comments are kept with no author. Signed-in users hold only SELECT and INSERT on the table, and anonymous users hold nothing, so neither can truncate it. The four functions are not callable anonymously. (#575)

  • 20260925120000_task_write_own_seat.sql changes user_can_write_quest_task() so a non-owner passes only for a task assigned to their own seat, never for an unassigned one. The INSERT and UPDATE policies that call it tighten with it and are renamed "Party members can insert tasks assigned to them." and "Party members can update tasks assigned to them." A new tasks_guard_placement trigger refuses a quest_id or sort_order change from anon or authenticated, while trusted roles still can. A client's new task now gets its position from the database: tasks_assign_sort_order ignores any value sent and calls the new next_task_sort_order(), which locks the quest row, so two tasks added at the same moment no longer share a position. Inserts made by trusted roles or inside SECURITY DEFINER functions such as clone_public_quest() keep the positions they choose. (#576)

Tests

  • New supabase/tests/task_write_permissions.sql (39 assertions) checks that a member can't edit, claim, unassign, move or delete tasks that aren't theirs, that nobody signed in can move or reorder a task, that a client's new task goes at the end whatever position it sends, that the owner still hands out work, that a trusted role can still move and position a task, and that a member holding tasks can still leave the party. New task_sort_order_concurrency.sql checks that two tasks added to a quest at once get different positions. guilds.sql and rls.sql now expect the stricter rule. (#576)

  • Test fixtures no longer use type casts to hide shapes that had drifted from the real types. The QuestTaskList task fixture still used assigned_to, due_date, order_index and owner_id, none of which are tasks columns any more, and lacked the current ones. It now matches the tasks row, so the typecheck fails if the two drift apart again. The model persona row fixture gained the columns it was missing, the attendance panel's party members are complete QuestPartyMember values, and the casts on the challenge fixtures, which already matched their types, were dropped. No component behaviour changed. (#578)

Documentation

  • docs/schema.md now has a table of who may create, update, assign, delete and move tasks, and its tasks section no longer refers to the removed assigned_to_user_id column. docs/api-endpoints.md and docs/due-dates.md no longer describe reordering. (#576)

  • The Error Codes table in docs/api-endpoints.md gives the tasks 403 a row per method and path, like every other row, instead of one row combining three methods, and each row now names the refusal that method actually returns. The coverage-threshold note in apps/web/vitest.config.ts said CI measures on Node 20; .nvmrc pins 22, and every workflow reads the version from it. (#650)