Skip to main content

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.

Two search requests return out of order; the current response is kept and the old response is ignored.
A constructed request race. Arrows show order, not measured latency.
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:

Code example
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.

Request 1 for ha starts before request 2 for harness. Request 2 finishes first and is displayed. When request 1 finishes later, a naive handler overwrites the page; an identity check ignores it.
Open full-size diagram
The failure depends on completion order, not a particular delay. This constructed timeline explains the bug; it does not report an observed DSH run.

Make the old response arrive last

Local simulation. Two searches have started; the current query is “harness”. Finish request 2, then request 1.

Request 1 · old

ha

Request 2 · current

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:

  1. Reproduce the race in the existing test runner, controlling when each request resolves.
  2. Guard both success and failure updates with the current request identity; connect clearing and cleanup to the same ownership rule.
  3. 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:

Code example
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:

Code example
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.

Idle becomes loading when a new request starts. Matching success becomes ready, matching failure becomes error, and clear returns to idle with a new identity. Old completions leave the current state unchanged.
Open full-size diagram
Reference state contract. Cancellation is a separate resource concern; identity decides which completion is allowed to change the 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.

Your practice record

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.