Lesson 7 of 19 · 11 min read
Walk DSH through a search race, from bug to review
A worked exercise across request handling, state, UI, and tests. Reproduce stale search results, plan a bounded repair, and verify both success and recovery paths.

Course syllabus · lesson 7 of 19
In this lesson
Treat this as a concurrency bug, not a prompting exercise. Type “ha,” then “harness.” The second search returns first; the slower response then overwrites it with results for “ha.” Both requests succeeded. The interface violated an ordering invariant.
This exercise turns that race into a bounded task for DSH. It spans a request adapter, state management, the results view, and tests, so the agent must trace a cross-layer invariant rather than patch one visible symptom. The diagrams are authored explanations, not application screenshots. The downloadable state example is a reference implementation tested locally; it is not output from a credentialed DSH run. Apply the prompts to a disposable copy of your own search feature, using its real paths and test commands.
Step 1: set up the evidence
Use the environment from the first-run lesson, then select the practice workspace. The official Web guide documents that selection and starting a session. The safety notice still applies: do not use a production account or expose sensitive data for this exercise.
Start in Standard mode as a working recommendation. Ask for investigation first:
Trace search input through the request adapter, state update, and result view.
Cite the actual files and nearest tests. Do not edit anything yet.
Find whether an older response can replace a newer query's results.
Identify the current loading, empty, error, clear, and unmount behavior.
Propose a deterministic reproduction; do not use arbitrary sleeps.
Do not install dependencies, change the API, commit, push, or deploy.
Verify the returned path against the repository. A credible map might be SearchBox → search controller → request adapter → result state → ResultsList. These names describe responsibilities, not files that DSH is guaranteed to find. Record the real names before allowing an edit.
Make the old response arrive last
Local simulation. Two searches have started; the current query is “harness”. Finish request 2, then request 1.
“ha”
“harness”
Displayed result
No response accepted yet
Only results for “harness” belong to the current query.
Completion order: choose a response above.
Toggle the check to compare the same completion order with and without the guard.
Step 2: agree on the contract
For this practice task, choose the following behavior before asking for code. A new query clears the previous results and error, then enters loading. Only that request may finish the loading state. An empty response becomes a visible empty state. A current failure becomes a retryable error. Clearing the input invalidates all pending work and returns to idle.
Request identities must be monotonically increasing within a controller instance. The controller assigns a new identity both for a new request and when clearing. Never recycle an identity while an older callback can still arrive. These are design decisions for this example, not DSH features.
Ask the agent to split the repair into three slices:
- Reproduce the race in the existing test runner, controlling when each request resolves.
- Guard both success and failure updates with the current request identity; connect clearing and cleanup to the same ownership rule.
- Verify visible loading, empty, error, retry, and keyboard behavior without changing the API or visual design.
Require a pause if the code has shared caches, pagination, or multiple search widgets. Their ownership rules may differ. A per-widget request counter is not a cache invalidation strategy.
Step 3: prove the state rule in isolation
Download the reference state function and its six tests into the same empty practice directory. With Node.js installed, run:
node --test search-state.test.mjs
The reference checks current success and empty results, stale success, stale failure, current failure and retry, clearing, and all six completion orders of three requests. It contains no network call and needs no DSH or provider key. Its expected result is six passing tests. That establishes the small state rule, not the correctness of your application's wiring.
The central guard is deliberately plain:
if (event.requestId !== state.requestId || state.status !== 'loading') {
return state;
}
For a red-to-green exercise, make a separate copy of the two files and remove that guard from the copied function. The stale-success, stale-failure, clear, and completion-order tests should fail. Give DSH that failing copy and the contract above; ask it to diagnose and repair the cause. Keep the working reference untouched so you can compare the result.
Do not paste this guard into the app and stop there. The adapter must carry the captured identity through both completion paths. Aborting obsolete network work can save resources, but a callback that has already completed still needs the ownership check. When the view is disposed, its controller must prevent later callbacks from updating that view.
Step 4: connect one layer at a time
Authorize the state and controller repair with its focused tests first. Ask DSH to show the changed paths and the test output before it touches the result view. Confirm that the identity is captured when the request starts, not read from mutable current state when it finishes.
Then check the UI integration. Loading should not leave an old error visible. An empty response should not look like a broken panel. Error feedback needs a usable retry path. Keep the input's label, keyboard behavior, and focus intact. Where the existing UI announces result status, retain that behavior without announcing every keystroke.
For browser verification, use the project's request interception or test adapter to hold two responses. Resolve the second, then the first, and inspect the rendered results. Repeat with a late rejection and with clearing during loading. This makes the failure repeatable without choosing a “slow enough” timeout.
Step 5: review the whole path
Use this evidence checklist for the completed patch:
- State: Focused tests fail without the guard and pass with it.
- Controller: Each request and clear gets a fresh identity; both completion paths preserve it.
- View: The newest results remain visible after an older completion.
- Recovery: Current errors can retry; clearing and leaving the view cannot publish old work.
- Regression: Existing tests, type checks, and browser checks pass with the original API and layout.
Ask for actual commands and exit codes, plus any checks that were not run. Inspect the final diff and confirm that no test was weakened. If the agent cannot control response order in the existing tests, that is an unresolved verification gap, not permission to call the race fixed.
Step 6: stop cleanly when evidence is missing
If the session is interrupted, inspect the files and active work before retrying. If the state tests pass but the browser still shows stale results, return to the controller-to-view connection. Do not add more guards until you can show which callback owns the incorrect update.
The DSH architecture makes tools and the agent loop composable. This exercise does not need a custom plugin: the useful work is tracing the existing system, making a bounded edit, and bringing back evidence. Reach for a plugin when you have a repeated capability to add, not merely because the task has several files.
Prove the concurrency invariant across layers
The request identity guard is only correct if the identity survives the whole path. Trace one search request through the input handler, request adapter, controller state, result reducer, and view. At each boundary, record the request id and the state version it is allowed to update. If one layer drops the id and another invents a new one, the guard can look present while the race remains.
Test more than the simple “second response wins” case. Include an empty query, a cleared query while a request is in flight, a rejected request after a newer request succeeds, cancellation during unmount, and a response that arrives after the component has been replaced. Decide which outcomes should update visible state and encode that decision in tests. “Ignore stale responses” is incomplete until you define what counts as stale after reset or cancellation.
Use a deterministic scheduler or deferred promises so the test controls completion order. Avoid a time-based sleep; it can pass while the race is still present on a busy machine. Add an assertion that the stale promise resolved, so the test proves the old request was handled and deliberately ignored rather than never completing.
For the review pass, compare the patch against the invariant in plain language: only the latest live request may publish results for the current query. Check the loading and error states too. A repair that protects the result list but leaves a stale spinner, error, or “no results” message still violates the user-visible state model.
Keep the prompt, starting revision, diff, and test output. Those artifacts let you repeat the exercise with a different mode or another harness without pretending a single patch establishes a universal winner.
Next, split independent work across agents without losing the shared contract or the evidence needed for review.
Before you move on
Try it in your workspace
Reproduce the search race with deterministic control over response order. Prove the invariant in state tests, then inspect the patch for cancellation, unmount, error, and loading regressions.
Keep a short note of what you tried, what passed, and what you still need to check.
0 of 3 checked.
Saved in this browser when storage is available. Uncheck any item to revisit it. This is your own record, not an assessment.