Address deep-research review follow-ups (#1864)#1876
Conversation
Code-review follow-ups from PR #1836: - mutations.ts: narrow CancelResearchReportOutput.obj.status from bare `string` to `JobStatus | string` to match ResearchReportType.status, so callers comparing against JobStatus values get type checking. - ResearchReportDetailTestWrapper: add maxUsageCount: Infinity to the detail query mock so the view's notifyOnNetworkStatusChange refetch/poll paths can't drain the single-response bucket (mirrors the sibling CorpusResearchReportCardsTestWrapper). - research/models.py: document that ResearchReport.duration_seconds is always a computed property (never stored); add a guard test pinning the invariant the status-tool duration tests depend on. Reviewer items verified to need no change: the async catch blocks in ResearchReportDetail.tsx / StartResearchModal.tsx already log via console.error; the slug column is already indexed (unique=True + db_index); and the snake_case reactive-var locals in CorpusResearchReportCards (opened_corpus / research_search_term / auth_token) intentionally match the established sibling-component convention (CorpusExtractCards / CorpusAnalysesCards) and avoid shadowing the imported reactive vars.
Code ReviewOverall: Clean, well-scoped follow-up PR. The changes are minimal and well-justified. The PR description's table (documenting which items were changed and which were intentionally left alone, and why) is a good model. Issue 1 — maxUsageCount in ResearchReportDetailTestWrapperCorrect fix. The detail view uses notifyOnNetworkStatusChange and can trigger refetches, so a fixed-bucket mock is fragile. This mirrors the pattern in CorpusResearchReportCardsTestWrapper. The inline comment explaining the reasoning is appropriate given the non-obvious test infrastructure behavior. Minor nit: Issue 2 — duration_seconds docstring + guard testNew test is solid. The three-step structure (create, assert None, set timestamps directly + assert computed value) cleanly validates the invariant. The docstring has a CLAUDE.md violation worth fixing. From CLAUDE.md:
The docstring currently names two specific test files ( """Wall-clock run time, always derived from timestamps — never stored.
If this is ever converted to a stored field, any test that sets
started_at / completed_at directly (bypassing finalize()) will need
to set the stored field explicitly instead.
"""Issue 5 — CancelResearchReportOutput.obj.status: JobStatus | stringCorrect alignment. Brings Context worth noting (not a change request): The new inline comment explaining why the type is Issues 3, 4, 6 — Non-changes, correctly assessedThe rejection rationale for issue 4 (snake_case locals) is correct — the sibling components (CorpusExtractCards, CorpusAnalysesCards) use the identical convention and renaming would shadow the imported camelCase reactive vars, causing a use-before-declaration error. Summary
One actionable item: trim the specific test-file names from the |
# Conflicts: # CHANGELOG.md
Narrow Optional return of duration_seconds before assertAlmostEqual to satisfy mypy arg-type check in the research report model test.
…ring Per CLAUDE.md, docstrings should not name specific callers/tests since those references rot as the codebase evolves. Keep the invariant and the migration-to-stored-field warning, drop the named test files.
Code ReviewOverviewTight follow-up PR that addresses four of six items from the PR #1836 review (#1864). The PR description is thorough and the reasoning for the two rejected items is sound. Changes are small and well-scoped. Feedback by file
Nit (pre-existing pattern, not introduced here): That said,
Correct fix.
The docstring is clear and the warning about stored-field conversion is exactly the right content for this invariant. No issues.
The test is well-structured. One minor style note on the double-assertion pattern: self.assertIsNotNone(duration)
assert duration is not None # narrow Optional for type-checkerTwo consecutive assertions for the same condition is redundant — if
Thorough entry with good cross-references. The extra blank line removal is a minor cleanup. SummaryAll four changes are correct and appropriately scoped. The reasoning in the PR body for the two rejected items (async catch blocks already log; slug already indexed) is well-documented and convincing. No blockers — the nits above are all optional. Good work on the |
|
Code Review: Address deep-research review follow-ups (#1864) Overall: Clean, well-scoped cleanup PR. The description is exemplary — it documents each issue, its verdict, and the rationale for changes and non-changes. The diff is minimal and purposeful. Strengths PR description discipline: The table mapping each issue to verdict and action is exactly the kind of traceability that makes review follow-ups easy to audit. The rationale for rejecting item 4 (snake_case locals) is well-argued and backed by codebase evidence (113 vs 75 uses in the wild, sibling components use identical pattern). Test quality (
Minor Observations Type narrowing in CHANGELOG.md entry placement: The new entry lands inside the existing Docstring in Summary No blocking concerns. The three changes are small, well-motivated, and leave the codebase in a better state — tighter types, a hardened test mock, and a pinned invariant with a guard test. The non-changes are correctly justified. Solid follow-up work. |
Summary
Resolves the actionable items from the PR #1836 code review tracked in #1864. Each reported issue was evaluated against the current
main(PR #1836 is now merged) before acting — two were already correct/fixed and one was based on an incorrect premise, so only the genuinely valid items were changed.Closes #1864
Assessment of the 6 reported issues
maxUsageCount: Number.POSITIVE_INFINITYduration_secondscomputed-property invariant undocumentedconsole.error(...)CorpusResearchReportCardsCancelResearchReportOutput.obj.statustypedstringJobStatus | stringunique=True+db_index=TrueChanges
frontend/src/graphql/mutations.ts—CancelResearchReportOutput.obj.statuswas the barestring; narrowed toJobStatus | stringto matchResearchReportType.statusso callers comparing againstJobStatusvalues get type checking on this payload.frontend/tests/ResearchReportDetailTestWrapper.tsx— the single detail-query mock now carriesmaxUsageCount: Number.POSITIVE_INFINITY(mirroringCorpusResearchReportCardsTestWrapper). The detail view usesnotifyOnNetworkStatusChangeand can refetch (terminal-notification path) or poll (non-terminal states), so a fixed-bucket mock risked a "No more mocked responses" error if a future test exercised a non-terminal state. (All current tests use terminal states, so this is hardening, not a live bug fix.)opencontractserver/research/models.py— documented thatResearchReport.duration_secondsis always a computed@property(never a stored field), since the status-tool duration tests setstarted_at/completed_atdirectly and depend on that invariant.opencontractserver/tests/research/test_research_report_model.py— addedtest_duration_seconds_computed_from_timestampspinning the invariant.Why #4 was rejected (snake_case locals)
The review claimed the codebase uses camelCase for these reactive-var locals. The opposite is true: the codebase uses snake_case for reactive-var shadow locals (≈113 vs 75), and the two direct sibling components —
CorpusExtractCards.tsxandCorpusAnalysesCards.tsx— use the identicalopened_corpus/auth_tokenpattern. The snake_case naming also deliberately avoids shadowing the imported camelCase reactive vars (openedCorpus, etc.); renaming the locals to camelCase would collide with the imports (const openedCorpus = useReactiveVar(openedCorpus)is a use-before-declaration error). Renaming would have been a regression, so it was left as-is.Verification
tsc --noEmit— clean (exit 0).yarn test:ct ResearchReportDetail.ct.tsx --reporter=list— 3/3 pass with themaxUsageCountchange.prettier --check— clean on the changed frontend files; husky lint-staged ran on commit.black/flake8— clean on the changed backend files. The new test mirrors a pattern already used by the type-checked (non-baselined)test_corpus_action_execution.py, so it is mypy-clean. (Full backend test suite was not run — Docker is unavailable in this environment — but the new test follows the same create→set-timestamps→save→assert pattern as the existing, passingtest_research_status_tool.pyduration tests.)