Questify

2.15.0

Released

Fixes

  • An investigation's images now come from the investigation form instead of from the quest. The route composed the list as the payload's images plus the quest's, and no client ever sent the payload field, so the quest's reference images were the only source: every investigation on an illustrated quest was surcharged ceil(base / 5) credits per quest image — a 4-credit Opus quest with five images cost 8 — for pictures the asker had not attached to the question, could not opt out of short of deleting them from the quest, and had not asked about. Nothing in production had been billed for this yet: 15 live quests, one with a single image, and the worker had never run. (#558)

  • The investigation dialog now has an image field, so an image can actually be attached to the question being asked. It takes the ten URLs the route's schema accepts, validates each one as it is added and shows a thumbnail, and says what an image adds to the credit cost. Until now the API's images field had no way to be filled. (#558)

  • The image editor now gives each instance its own field id. It was written for the quest form with a hard-coded image_url, and reusing it in the investigation dialog means two copies can be mounted on one page — where both labels point at whichever input rendered first, so clicking the second one focuses the wrong editor and the duplicate id is invalid markup besides. The id is now per instance. (#585)

  • A finished task investigation now shows the model's whole answer instead of the first 500 characters of it. The card only ever rendered task_investigations.summary, which is a one-line label the worker cuts from the first line of the response and ends with an ellipsis, so any answer longer than that — every one of them in practice — was unreadable past that point on a surface the account had already been charged for. The full text was on the row the whole time under details.result.text; the summary is now the fallback for a row with no answer yet, which is what pending, running and failed rows and anything written before the worker recorded a result show. (#581)

  • The feedback dialog no longer pushes its own buttons off the screen. The panel had no height bound and the overlay it sits in is position: fixed, so once the form grew — a screenshot preview, or the validation messages a submission of an empty form produces — the close button and the Cancel/Send pair ended up below the bottom of the viewport with nothing to scroll to reach them, and on a phone there was no way to send the feedback or dismiss the dialog. The panel is now bounded by the overlay's own content box and only the field column scrolls, so the header and the actions stay put however tall the form gets. (#557)

  • A rejected feedback submission now takes the user to the field it was rejected on, instead of leaving them at a footer that did not visibly change. The field can sit above the fold of the scrolling body. It covers every field a rejection can name — including a screenshot the storage upload rejected, which until now reported its failure and left the user looking at whatever part of the form they happened to be scrolled to. A form-level error names no field and leaves focus where it is, since it renders in the footer. (#557)

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

Tests

  • Pinned where an investigation's images come from, which nothing covered before: the route test now stands up a Supabase and an entitlement stub and asserts that a quest carrying images contributes none to the stored column, to the prompt, or to the image count the charge is priced from, that the form's images do reach all three, that a duplicate is collapsed before it is priced, and that the ten-image cap and URL validation reject a payload rather than trimming it. The dialog sends what was attached and drops what was removed, the task manager omits the field entirely when nothing is attached, the shared image editor reports its list to a caller that posts JSON itself and takes its cap from that caller's schema, and getAccessibleTaskInvestigationContext() no longer selects the quest's images. Two adds fired back to back keep both entries and report each, and a duplicate typed straight after the first add is still caught — the image editor derives its next list from the current render rather than from a setUrls updater, because the update it commits also calls onChange, and a side effect inside an updater runs twice under reactStrictMode. (#558)

  • Pinned that two image editors on one page keep their own labels: each input carries a distinct id and each label points at the input beside it, which the hard-coded id failed. (#585)

  • Pinned the investigation card's answer against the clipped summary: the whole response renders and the truncated summary does not, the summary still stands in while a row has no answer, and the reader of details.result.text holds up against every shape a Json column can arrive in — a missing result, a blank answer, and a details or result value that is not an object at all. (#581)

  • Pinned the feedback dialog's layout contract, which no existing test could see: the old markup rendered every one of the same elements, just off the bottom of the screen. The panel is bounded and clipped, the field column is the only scrolling region, the close and action controls sit outside it, a form-level error renders beside the buttons rather than at the end of the scroll, and every rejection path focuses the field it names — local validation, each field the server can reject, a failed screenshot upload — while a form-level error leaves focus alone. (#557)