Dentolize · Composable Sandbox QA Walkthrough
On this pageA note on what you're about to seePart 1 — The dispatch formPart 2 — What lands on the pull requestPart 3 — What the agent actually drivesPart 4 — Where the evidence goes

Feature Tour

A note on what you're about to see

This PR's own interface lives on GitHub — the workflow dispatch form, the AI's pull-request comments, the story issues. Those screens could not be captured for this document. The environment that generated these docs has no authenticated GitHub session, and the repository is private, so there is no honest way to produce a screenshot of them. Rather than fake a mockup, this page does two things instead:

  1. Reproduces the workflow's GitHub output exactly, rendered from the

template strings in the code. Where you see a comment or issue below, every character of its structure comes from .github/workflows/sandbox-qa.yml — only the AI-authored prose and the numbers are illustrative.

  1. Shows real screenshots of the surface the agent actually drives — the

live sandbox for this very branch, ci-qa-composable, captured with the same kind of browser automation the test action uses.

The second half is the more useful one anyway. The workflow is plumbing; what it does is walk a real clinic system.


Part 1 — The dispatch form

Actions tab → Sandbox QARun workflow. Two dropdowns, defined at .github/workflows/sandbox-qa.yml:23-44:

Use workflow from
  Branch: ci/qa-composable          ← the branch that gets reviewed

QA action to run                    (required, default: test)
  ○ review
  ○ plan
  ● test
  ○ fix
  ○ full-auto

Test scope (test action only)       (required, default: all)
  ● all
  ○ failed-only

Three things to notice, all visible in the code:

  • test is the default (:30) — the default button does not commit to your

branch.

  • scope is shown for every action, including the four it doesn't apply to

(:37-44). GitHub Actions has no conditional inputs; the description carries the caveat.

  • The branch selector is the real target. The workflow runs on the ref you

pick, not on whichever PR you have open in another tab.


Part 2 — What lands on the pull request

After review

One comment. Structure verbatim from .github/workflows/sandbox-qa.yml:131-138:

<!-- sandbox-qa-review -->            ← invisible; do not delete when editing
### 🔍 QA code review (AI — edit freely; `plan` builds on your edits)

_2026-07-20 16:40 UTC · [run](…/actions/runs/12345) · branch `ci/qa-composable`_

…the agent's technical and business review…

The header text is not a paraphrase — that string is at line 132. The invitation to edit is part of the product.

This is the artifact that did not exist before. Previously the earliest thing a human could correct was the test plan, already built on an unseen understanding of the code.

After plan

Same shape, different marker and header (:127, :133):

<!-- sandbox-qa-plan -->
### 🧪 QA test plan (AI — edit freely; `test` runs exactly this)

_2026-07-20 16:52 UTC · [run](…) · branch `ci/qa-composable`_

…user stories, each with a role and a priority…

Edit this comment, dispatch test, and your edited text is what runs — the workflow reads the comment body back from the API at :76-82.

After test

One issue per story. Title and body from :191-200:

[QA-7] ❌ Receptionist can move an appointment into a fully booked slot

**Result: ❌ FAIL** · role `receptionist` · priority normal
PR: #241 · tested 2026-07-20 17:31 UTC · [run](…)

## User story
As a receptionist, I open the calendar, drag an existing appointment onto a
slot that already has two bookings, and expect to be warned before it saves.

## Test result
No warning appeared; the appointment saved silently. Screenshot attached in the
run artifacts.

Labels: ai-automated, pr-241, plus critical if the story is critical-priority (:199).

Passing, fixed, and skipped stories are created and then immediately closed (:176, :203-205) — so the open issues are "what needs attention", with the caveat that a skipped story closes too.

Plus one summary comment (:230-239):

<!-- sandbox-qa-report -->
### 🧪 QA review

_2026-07-20 17:31 UTC · [run](…) · mode **test** · 12 stories: 9 ✅ · 2 ❌ · 0 🔧 · 1 ⏭️_

Full story-by-story results: [`ai-automated` + `pr-241` issues](…) (open = needs attention)

…the agent's short prose summary…

<details><summary>Story issues</summary>

❌ #310 QA-7 · ✅ #311 QA-8 · ⏭️ #312 QA-9 · …

</details>

That tally line is the number to trust — it is the only place skipped stories are visible.

After fix

No new issues. Each existing one gets a comment and a retitle (:179-187):

**Re-test: 🔧 FIXED** · 2026-07-20 18:10 UTC · [run](…)

The overbooking guard was not applied to the drag-and-drop path, only to the
create-appointment form. Applied it to both; the warning now appears.

…and the issue is retitled [QA-7] 🔧 Receptionist can move an appointment… and closed.


Part 3 — What the agent actually drives

Everything above is bookkeeping. This is the system the test action walks: the live sandbox built from this branch, at ci-qa-composable.sandbox.anastawfik.com. These screenshots were captured through browser automation against that environment — the same route the agent takes.

It starts where any user starts

The sandbox login screen for the ci/qa-composable branch — a complete, isolated deployment of Dentolize built from the PR head commit
The sandbox login screen for the ci/qa-composable branch — a complete, isolated deployment of Dentolize built from the PR head commit

Every pull request gets one of these: a full stack — API, web app, database — routed at its own hostname and destroyed when the PR closes. The QA agent receives credentials for it and logs in like a person would.

The clinic the agent sees

The owner's dashboard: today's appointments, outstanding invoices with payment progress bars, and the date and doctor filters
The owner's dashboard: today's appointments, outstanding invoices with payment progress bars, and the date and doctor filters

This is the owner account — all permissions. The data is entirely synthetic (you'll see "Patient 006" throughout), seeded fresh into every sandbox. The two invoices visible here, at 0% and 15% paid, are the kind of state a story like "an accountant records a partial payment" would act on.

Patients

The patient list with search, filters, and per-patient summary rows
The patient list with search, filters, and per-patient summary rows

The calendar

The appointment calendar — the surface behind most scheduling user stories
The appointment calendar — the surface behind most scheduling user stories

Scheduling stories are where the agent spends much of its time: booking, rescheduling, double-booking, no-shows, waiting lists. The dashboard's appointment panel exposes ten statuses — Open, Confirmed, Checked In, In Progress, Completed, Delayed, Canceled, No Show, Waiting List, Note — and each is a transition a story can assert on.

Invoices

The invoices screen, with payment progress, VAT handling, and per-invoice actions
The invoices screen, with payment progress, VAT handling, and per-invoice actions

Financial flows are the highest-stakes area the agent touches, which is why the plan's priority field matters — critical stories get an extra critical label on their issue (.github/workflows/sandbox-qa.yml:199).

Roles are part of the test, not a detail

Every user story carries a role (.github/workflows/sandbox-qa.yml:195), because what a receptionist can reach is not what an owner can reach. The sandbox seeds a full roster — owner, two doctors on different commission structures, a receptionist, an accountant, an HR manager — all sharing one password so the agent can switch between them.

The two-step username login: clinic login name, then user credentials — how the agent assumes a specific staff role
The two-step username login: clinic login name, then user credentials — how the agent assumes a specific staff role
The same dashboard as the receptionist: Inventory and Analytics are absent from the sidebar, and the header identifies the role and its permission set
The same dashboard as the receptionist: Inventory and Analytics are absent from the sidebar, and the header identifies the role and its permission set

Compare that sidebar to the owner's earlier in this page. The owner has Inventory and Analytics; the receptionist does not. A story that says "a receptionist cannot reach inventory" is asserting on exactly this difference — which is why the plan's role field is one of the fields most worth correcting by hand before dispatching test.

And down into the detail

A patient profile — the depth of state a single user story may need to set up and assert on
A patient profile — the depth of state a single user story may need to set up and assert on

Part 4 — Where the evidence goes

Every run attaches an archive to itself: the agent's full log, the untruncated review and plan, the raw stories.json, and the rest of the branch's QA state directory. Kept 14 days (.github/workflows/sandbox-qa.yml:241-250).

This is where the fix in this PR shows up. The archive used to be named after the raw branch name — and GitHub forbids / in artifact names. On ci/qa-composable, or any feat/… branch, the upload silently failed and the evidence for that run was lost. The workflow now names it from a sanitized slug the agent emits (:247), which is the same slug the sandbox hostname uses: ci-qa-composable.

One caveat worth knowing before you go looking: the upload only runs if the agent got far enough to report its state directory (:242). An agent that crashes on startup produces no artifact at all — including the log that the error message tells you to read. In that case, the raw job log in the Actions tab is what you have.