Skip to content

[Enhancement] - Unify types for full stack#294

Open
leekahung wants to merge 15 commits intomainfrom
issue-259/unify-types-for-whole-stack
Open

[Enhancement] - Unify types for full stack#294
leekahung wants to merge 15 commits intomainfrom
issue-259/unify-types-for-whole-stack

Conversation

@leekahung
Copy link
Contributor

@leekahung leekahung commented Mar 3, 2026

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update
  • Infrastructure
  • Maintenance

Description

Follow up to PR #283, this PR adds a codegen script generate_types.py to generate frontend TypeScript types directly from backend Pydantic models and StrEnums so it wouldn't stray from the types set by the backend as suggested in Issue #259.

It covers type definitions for API response chunks and locations used in the frontend. It also covers the naming convention set for the frontend, I for interface and T for type. To generate the frontend types, a new make command is added to the makefile in order to run make generate-types.

To update the types, we only need to add a new Chunk type to TResponseChunk via | in schema.py or a location via | in location.py and re-run make generate-types.

Several types in the frontend are made tighter as opposed to generic strings, and a new helper function is created to simplify location formatting.

Related Tickets & Documents

QA Instructions, Screenshots, Recordings

To verify the codegen, you can remove the frontend types and run make generate-types from the backend directory. In the frontend run: npm run lint, npx tsc -p tsconfig.app.json --noEmit, npm run test -- --run (for testing the new import location for types)

Added/updated tests?

  • Yes
  • No, and this is why: please replace this line with details on why tests have not been included
  • I need help with writing tests

Documentation

  • If this PR changes the system architecture, Architecture.md has been updated

[optional] Are there any post deployment tasks we need to perform?

@github-actions

This comment was marked as outdated.

@leekahung leekahung changed the title Issue 259/unify types for whole stack [Enhancement] - Unify types for full stack Mar 3, 2026
@github-actions

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Claude finished @leekahung's task in 2m 17s —— View job


Code Review

  • Gather context
  • Review current state of changed files
  • Implement remaining fixes
    • Add direct test for non-Optional union in test_generate_types.py
  • Compile and post feedback

Good round. All the previously flagged issues have been addressed. I committed one remaining fix and have one item left that requires a manual change.

Committed Fix

test_generate_types.py — Added a direct test for the X | Y (non-Optional) union branch in py_annotation_to_ts:

def test_py_annotation_to_ts_union_without_none():
    result = py_annotation_to_ts(OregonCity | UsaState)
    assert result == "TOregonCity | TUsaState"

Previously only OregonCity | None exercised this branch indirectly through test_model_to_interface_location. This makes the coverage explicit.


Still Requires Manual Fix

pr-check.yml — floating action tags (fourth time, cannot be auto-fixed)

actions/checkout@v6 (lines 35 and 102) and actions/setup-node@v6 (line 105) are still floating major-version tags. Note @v6 also looks like a version error — both actions are currently at v4. Per CLAUDE.md, these must be pinned to commit SHAs. I cannot modify workflow files directly, so this needs a manual commit:

# actions/checkout v4.2.2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2

# actions/setup-node v4.3.0
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e #v4.3.0

Addressed ✅

  • formHelper.ts:30"I'm in ." bug fixed (conditional spread)
  • InitializationForm.tsx:152 — outer template literal removed
  • test_model_to_interface_location — exact field line assertions (" city: TOregonCity | null;")
  • Location docstring — documents state=None treated as UsaState.OTHER
  • handleLocationChange double-cast — eliminated, single typedKey reused
  • formHelper.test.ts — empty-location test case added
  • InitializationForm.tsx:65-66?? applied instead of ||
  • test_generate_types.py — direct test for non-Optional union added (this round)
  • generate_types.py — validates types_dir before writing
  • CHUNK_MODELS — explicit list[type[BaseModel]] annotation
  • CI type-drift check is in place under backend-test
  • Architecture.md — updated and typo fixed
  • make_file unused imports parameter removed

The PR is solid. The codegen approach is clean, the CI drift check is a good enforcement mechanism, and the type tightening across the frontend is a clear improvement. Just the workflow pinning left to do manually.

@leekahung leekahung self-assigned this Mar 3, 2026
@leekahung leekahung added backend Bot implementation and other backend concerns frontend Frontend implementation (follows UX design) labels Mar 3, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Claude finished @leekahung's task in 3m 45s —— View job


Code Review

  • Gather context
  • Review backend changes
  • Review frontend changes
  • Review tests
  • Compile and post feedback

This is a solid, well-structured PR. The codegen approach is clean, the CI drift check is good defensive infrastructure, and the type tightening across the frontend is a clear improvement. All previously flagged issues have been resolved.

What's working well

  • generate_types.py — Clean, focused script with good error messages and sensible scope (the docstring explicitly notes which annotation types are supported and how to extend). The HEADER banner makes the generated files self-documenting.
  • CI drift check (pr-check.yml:87-90) — Generating types in CI and checking git diff --exit-code is a strong enforcement mechanism. Anyone who edits the generated files by hand or forgets to re-run codegen will see an immediate failure.
  • Type tighteningCitySelectOptionType using TOregonCity | null / TUsaState | null instead of plain strings is a genuine improvement. TypeScript will now catch any future mismatch if enum values change and codegen is re-run.
  • formatLocation.ts — A clean, well-tested single-purpose function. The conditional spread in formHelper.ts:30 correctly prevents the "I'm in ." bug.
  • TCitySelectKey enforcementRecord<TOregonCity | "oregon" | "other", CitySelectOptionType> means TypeScript will require an entry for any new TOregonCity value, which is good.

Remaining Issues

pr-check.yml — pre-existing floating action tags (not introduced by this PR)

Confirmed: actions/checkout@v6 (lines 35 and 102) and actions/setup-node@v6 (line 105) were already on main before this PR. This PR only adds the drift check step. That said, per CLAUDE.md, these need to be pinned to commit SHAs eventually. @v6 also looks like a version error — both actions are at v4.

# actions/checkout v4.2.2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2

# actions/setup-node v4.3.0
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e #v4.3.0

This can be tracked separately rather than blocking this PR, but it should be addressed before the next release.


Minor Nits

formatLocation.test.ts — missing (null, null) base case

The four existing tests are good, but the most basic case — both args null returns empty string — isn't directly tested. Fix this →

it("returns empty string when both city and state are null", () => {
  expect(formatLocation(null, null)).toBe("");
});

generate_types.py:43-44type(None) called twice

ts_parts = [py_annotation_to_ts(a) for a in args if a is not type(None)]
if type(None) in args:

type(None) is evaluated at runtime each call. A module-level _NoneType = type(None) would make the intent explicit and avoid the repeated call. Very minor.


Previously Flagged — All Addressed ✅

  • formHelper.ts:30"I'm in ." bug fixed (conditional spread)
  • InitializationForm.tsx:152 — outer template literal removed
  • test_model_to_interface_location — exact field line assertions
  • Location docstring — documents state=None treated as UsaState.OTHER
  • handleLocationChange — single typedKey, ?? used
  • formHelper.test.ts — empty-location test case added
  • test_generate_types.py — direct test for non-Optional union (OregonCity | UsaState)
  • CHUNK_MODELS — explicit list[type[BaseModel]] annotation
  • generate_types.py — validates types_dir before writing
  • Architecture.md — updated with generate_types.py entry and types/ directory note

The PR is ready. The nits above are non-blocking. The workflow pinning is pre-existing debt worth tracking separately.

@leekahung leekahung marked this pull request as ready for review March 3, 2026 22:38
@leekahung leekahung requested review from wittejm and yangm2 March 3, 2026 22:38
@leekahung
Copy link
Contributor Author

@claude Can you add a section in the review about files changed for package/lockfiles vs code changes

@claude
Copy link
Contributor

claude bot commented Mar 4, 2026

Claude finished @leekahung's task in 1m 11s —— View job


Code Review

  • Gather context
  • Review package/lockfile changes
  • Update review comment with file breakdown section

Changed Files Breakdown

The 24 changed files split cleanly into three categories:

Package / Lockfile (1 file, not a code review concern)

File +/- Notes
frontend/package-lock.json +169 / -101 Re-applies npm vulnerability patches that were lost during the #283 merge (commit cfa79e1). No package.json changes — this is purely a lockfile resync via npm audit fix, not a dependency addition.

Nothing to review here; this is expected housekeeping.

Infrastructure / Config / Docs (4 files)

File Notes
.github/workflows/pr-check.yml Adds the generated-type drift check under backend-test
Architecture.md Updated to document generate_types.py and the types/ directory
backend/Makefile Adds make generate-types target
frontend/package-lock.json (see above)

Code Changes (19 files)

Backend (3 files)

File Notes
backend/scripts/generate_types.py New codegen script — the core of this PR
backend/tenantfirstaid/location.py Adds docstring clarifying state=NoneUsaState.OTHER
backend/tests/test_generate_types.py New tests for the codegen script

Frontend — new files (4 files)

File Notes
frontend/src/shared/utils/formatLocation.ts New helper for city/state formatting
frontend/src/tests/utils/formatLocation.test.ts Tests for formatLocation
frontend/src/types/LocationTypes.ts Generated — do not edit by hand
frontend/src/types/MessageTypes.ts Updated to tighten chunk types

Frontend — updated files (11 files)

File Notes
frontend/src/Letter.tsx Type import update
frontend/src/contexts/HousingContext.tsx Tighter types, uses generated enums
frontend/src/hooks/useMessages.tsx Tighter types
frontend/src/pages/Chat/components/InitializationForm.tsx Uses TOregonCity/TUsaState, ?? fix
frontend/src/pages/Chat/components/InputField.tsx Type import updates
frontend/src/pages/Chat/components/MessageWindow.tsx Type import updates
frontend/src/pages/Chat/utils/formHelper.ts Uses formatLocation, fixes "I'm in ." bug
frontend/src/pages/Chat/utils/streamHelper.ts Tighter chunk type handling
frontend/src/pages/Letter/utils/letterHelper.ts Uses generated types
frontend/src/shared/constants/constants.ts TCitySelectKey record type enforcement
frontend/src/tests/components/HousingContext.test.tsx Updated for new types
frontend/src/tests/utils/formHelper.test.ts Adds empty-location test case
frontend/src/tests/utils/streamHelper.test.ts Updated for new types

Previously Raised Items

All prior issues have been addressed. One remaining nit and one pre-existing debt item:

NitformatLocation.test.ts is missing a (null, null)"" base case. Non-blocking. Fix this →

Pre-existing debtactions/checkout@v6 (lines 35, 102) and actions/setup-node@v6 (line 105) in pr-check.yml are floating major-version tags (and @v6 appears to be a version error — both actions are at v4). These predate this PR and can be tracked separately, but should be pinned to commit SHAs per CLAUDE.md before the next release.

leekahung and others added 10 commits March 4, 2026 13:32
… based on schema.py and location.py; Tightened frontend types; Updated unit tests and files to import new types; Included helper function to help format city/state
…t as suggested; Added type to CHUNK_MODELS; Have other return as null instead of OTHER; Updated unit tests
…usted unit test; Updated comment for Location class; Include comment for model_to_interface based on feedback suggestion
Co-authored-by: Ka Hung Lee <undefined@users.noreply.github.com>
@leekahung leekahung force-pushed the issue-259/unify-types-for-whole-stack branch from fbde32d to fbba60e Compare March 4, 2026 21:33
@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

Claude finished @leekahung's task in 2m 45s —— View job


Code Review

  • Gather context
  • Review backend changes
  • Review frontend changes
  • Review tests
  • Compile and post feedback

This PR is in good shape. All major issues from prior rounds have been resolved. Here's a fresh pass over the current state.


Changed Files Breakdown

Package / Lockfile — not a code review concern

frontend/package-lock.json was re-synced via npm audit fix in an earlier commit. No package.json additions — just lockfile housekeeping from the #283 merge.

Infrastructure / Config / Docs (3 files)

File Notes
.github/workflows/pr-check.yml Adds type-drift check under backend-test
Architecture.md Documents generate_types.py, types/ directory, formatLocation.ts, and updates code snippet to match new API
backend/Makefile Adds make generate-types target

Code Changes (20 files)

Backend (3 files)

File Notes
backend/scripts/generate_types.py New codegen script
backend/tenantfirstaid/location.py Adds Location Pydantic model + docstring
backend/tests/test_generate_types.py Tests for the codegen script

Frontend — new files (4 files)

File Notes
frontend/src/shared/utils/formatLocation.ts Formatting helper for TOregonCity/TUsaState
frontend/src/tests/utils/formatLocation.test.ts Tests for formatLocation
frontend/src/types/LocationTypes.ts Auto-generated — do not edit by hand
frontend/src/types/MessageTypes.ts Updated with auto-gen header, tightened exports

Frontend — updated files (13 files)

File Notes
frontend/src/Letter.tsx Import moved to types/LocationTypes
frontend/src/contexts/HousingContext.tsx string → tighter derived types; ILocation import moved
frontend/src/hooks/useMessages.tsx addNewMessage now takes ILocation; `state
frontend/src/pages/Chat/components/InitializationForm.tsx Uses formatLocation, ??, tighter casts
frontend/src/pages/Chat/components/InputField.tsx addMessage prop types tightened
frontend/src/pages/Chat/components/MessageWindow.tsx Same
frontend/src/pages/Chat/utils/formHelper.ts Uses formatLocation, fixes empty-location bug
frontend/src/pages/Chat/utils/streamHelper.ts Passes housingLocation directly
frontend/src/pages/Letter/utils/letterHelper.ts Uses formatLocation
frontend/src/shared/constants/constants.ts CitySelectOptionType tightened; TCitySelectKey, THousingType, TTenantTopic exported
frontend/src/tests/components/HousingContext.test.tsx Values updated to lowercase enum strings
frontend/src/tests/utils/formHelper.test.ts Values updated; empty-location test added
frontend/src/tests/utils/streamHelper.test.ts Values updated to lowercase

What's Working Well

  • generate_types.py — Clean, focused script. The HEADER banner makes generated files self-documenting. The if not types_dir.is_dir() guard provides a clear error message.
  • CI drift check — Generating types in CI and running git diff --exit-code on the output is exactly the right enforcement mechanism.
  • Type tighteningcity: TOregonCity | null and state: TUsaState | null replacing string | null throughout is a genuine improvement. TypeScript will catch value mismatches if enum values change and codegen is re-run.
  • formatLocation.ts — Well-tested, single-purpose function. The conditional spread in formHelper.ts:30 correctly eliminates the "I'm in ." bug.
  • useMessages.tsx:41 — Replacing state: string (with || "" fallback) with { city, state }: ILocation is cleaner and semantically correct. UsaState.from_maybe_str(null) in chat.py:56 correctly resolves nullUsaState.OTHER, so passing null state is safe.
  • Location model in location.py — Well-documented; the docstring explicitly states its purpose and the state=NoneUsaState.OTHER semantics.

Issues

Minor — formatLocation.test.ts missing (null, null) base case

The four existing tests cover the non-trivial paths well. The most basic case — both args null — returns "" but isn't directly tested. Fix this →

it("returns empty string when both city and state are null", () => {
  expect(formatLocation(null, null)).toBe("");
});

Minor — generate_types.py:43-44type(None) called twice

ts_parts = [py_annotation_to_ts(a) for a in args if a is not type(None)]
if type(None) in args:

type(None) is evaluated at runtime on each call. A module-level _NoneType = type(None) would make the intent explicit. Very minor.

Pre-existing — pr-check.yml floating action tags (not introduced by this PR)

actions/checkout@v6 (lines 35, 102) and actions/setup-node@v6 (line 105) predate this PR. @v6 also appears to be a version error — both actions are currently at v4. Per CLAUDE.md these should be pinned to commit SHAs. Can be tracked separately.

# actions/checkout v4.2.2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2

# actions/setup-node v4.3.0
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e #v4.3.0

Note: I cannot modify workflow files directly — this needs a manual commit.


All Previously Flagged Issues — Addressed ✅

  • formHelper.ts:30"I'm in ." bug fixed
  • InitializationForm.tsx:152 — outer template literal removed
  • test_model_to_interface_location — exact field line assertions
  • Location docstring — documents state=NoneUsaState.OTHER
  • handleLocationChange — single typedKey, ?? used
  • formHelper.test.ts — empty-location test case added
  • test_generate_types.py — direct test for non-Optional union added
  • CHUNK_MODELS — explicit list[type[BaseModel]] annotation
  • generate_types.py — validates types_dir before writing
  • Architecture.md — updated with codegen entry, types/ note, code snippet updated
  • make_file unused imports parameter removed

The PR is ready. The two remaining items are non-blocking nits. The workflow pinning is pre-existing debt.

@leekahung
Copy link
Contributor Author

Rebasing branch, deferring NPM fixes to PR #296

Copy link
Member

@KentShikama KentShikama left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yangm2 @wittejm Just a note that Claude is projected to cost me $190/month on this repo for March at this rate. I'm happy to cover that but it has gone up by 5x or so recently and I just wanted to let you know that I probably can't afford another 5x. I wonder if we can switch to the $200/month subscription flat plan somehow.

@leekahung
Copy link
Contributor Author

@yangm2 @wittejm Just a note that Claude is projected to cost me $190/month on this repo for March at this rate. I'm happy to cover that but it has gone up by 5x or so recently and I just wanted to let you know that I probably can't afford another 5x. I wonder if we can switch to the $200/month subscription flat plan somehow.

Think we manually trigger Claude for review so we could limit the cost as opposed to running it each commit. We already have access to Claude using mentions

@wittejm
Copy link
Contributor

wittejm commented Mar 4, 2026

Dang it didn't occur to me where the bill was going. @leekahung @yangm2 @lee-coates do y'all have a coding assistant subscription? I have Claude Code. It might be better practice anyway for reviews to be human-in-the-loop via the coding assistant.

@leekahung
Copy link
Contributor Author

Dang it didn't occur to me where the bill was going. @leekahung @yangm2 @lee-coates do y'all have a coding assistant subscription? I have Claude Code. It might be better practice anyway for reviews to be human-in-the-loop via the coding assistant.

Yeah, I can look into getting a subscription on my end

Comment on lines +1 to +13
// This file is auto-generated by backend/scripts/generate_types.py.
// Do not edit manually — re-run `make generate-types`.

type TOregonCity = "portland" | "eugene";

type TUsaState = "or" | "other";

interface ILocation {
city: TOregonCity | null;
state: TUsaState | null;
}

export type { TOregonCity, TUsaState, ILocation };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't check-in generated code unless there's a really good reason (i.e. takes hours to generate). If you don't check it in, then you don't have to have a process to check for drift.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add the files under .gitignore and add instructions for generating types for the frontend

@yangm2
Copy link
Contributor

yangm2 commented Mar 5, 2026

@yangm2 @wittejm Just a note that Claude is projected to cost me $190/month on this repo for March at this rate. I'm happy to cover that but it has gone up by 5x or so recently and I just wanted to let you know that I probably can't afford another 5x. I wonder if we can switch to the $200/month subscription flat plan somehow.

Think we manually trigger Claude for review so we could limit the cost as opposed to running it each commit. We already have access to Claude using mentions

Just to clarify, we weren't automatically running it on each commit, it was on each git push (could be 1 or 100 commits).

@yangm2
Copy link
Contributor

yangm2 commented Mar 5, 2026

Dang it didn't occur to me where the bill was going. @leekahung @yangm2 @lee-coates do y'all have a coding assistant subscription? I have Claude Code. It might be better practice anyway for reviews to be human-in-the-loop via the coding assistant.

Would be better to have this discussion on Discord where it's more visible. I just happened to peek at this PR and noticed this.

…nore; Updated frontend npm script to include generate-types; Adjusted pr-check.yml to setup type generation
@yangm2
Copy link
Contributor

yangm2 commented Mar 5, 2026

@yangm2 @wittejm Just a note that Claude is projected to cost me $190/month on this repo for March at this rate. I'm happy to cover that but it has gone up by 5x or so recently and I just wanted to let you know that I probably can't afford another 5x. I wonder if we can switch to the $200/month subscription flat plan somehow.

@KentShikama You should probably revoke the API key that you use for this repo. I see that there are hundreds of "unique" visitors cloning the repo in the last 2 weeks. If someone is exploiting the repo, then it will take some time to clean out of the git history.

@leekahung leekahung requested a review from yangm2 March 5, 2026 00:43
@leekahung leekahung force-pushed the issue-259/unify-types-for-whole-stack branch from f9c38d4 to c2927ea Compare March 5, 2026 00:47
@KentShikama
Copy link
Member

@yangm2 The API key is a secret on this repo and I think you have to tag Claude on an issue/PR/workflow to trigger it. I think all usage is legitimate so far.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Bot implementation and other backend concerns frontend Frontend implementation (follows UX design)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants