Skip to content

feat(close): the schedule form takes custom curves and loans, previews, and edits in place - #524

Merged
jfrench9 merged 2 commits into
mainfrom
feature/schedule-curves-and-editing
Oct 22, 2026
Merged

jfrench9 merged 2 commits into
mainfrom
feature/schedule-curves-and-editing

Conversation

@jfrench9

@jfrench9 jfrench9 commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Summary

Phase 4 of the schedule work: the app side of what robosystems v1.20.0 shipped (custom curves, loan amortization, preview-schedule, rebuild-schedule with an edited definition; rs #1922, live on prod since 2026-10-22).

  • ScheduleModal replaces NewScheduleModal, with a method: straight-line as before; a custom curve (one amount per month of the range, spread evenly until typed into, refused until it sums to the total less salvage); a loan, whose terms (principal, rate, draw date, day count, payment frequency and amount, interest-only, maturity, capitalized interest, the note and current-portion accounts) the server turns into the amortization table, with the entry's legs set by how the payment settles (accrued and paid through the bank feed, or booked whole by the schedule).
  • Preview runs the server's generator without keeping it and shows the roll-forward month by month (a loan's interest, payment, principal, current portion), the overlap warnings, and on an edit what the schedule has posted and the path the change takes. Any edit to the form clears it.
  • Edit schedule on the panel loads the definition back from the envelope's artifact.mechanics (the facts' months and usual amount for a schedule stored before the definition rode the envelope) and saves it through rebuild-schedule while nothing has posted, renaming through update-schedule; once a month has posted the save is locked with the server's edit_warning.
  • The schedule rendering reads a loan as the note's balance moved by the period's interest, instead of by the balance difference (zero for an interest-only note).
  • Everything goes through the generated SDK in src/lib/ledger/schedules.ts, so this builds against the published client 2.31.0 and waits on no client release. The facade twins are feat(ledger): schedule facade takes custom curves, loans, preview, and an edited definition robosystems-typescript-client#295 and feat(ledger): schedule facade takes custom curves, loans, preview, and an edited definition robosystems-python-client#279, for SDK users.

Acceptance from the spec, run read-only through preview-schedule against a live interest-only line of credit: the schedule reproduces the lender's billed interest to the cent on actual/360 for the closed months, states the note's balance throughout, and raises no overlap warning.

Not verified in a browser: the local stack still runs the 1.19.6 image, which predates the operations.

Test plan

  • ScheduleModal: straight-line behaviours ported (13), custom curve (spread, mismatch refused, negative refused), loan (terms + bank legs, schedule-settled legs, draw date outside the first period refused, preview renders and clears on edit), edit (prefill + rebuild, rename, preview against the replaced schedule, posted lock, legacy fallback, loan loads back)
  • SchedulePanel: Edit opens on the envelope's definition; no Edit without one
  • ScheduleRendering: a loan reads the note and the interest
  • schedules helpers: months, life end, even curve, mechanics narrowing, facts fallback, legs ↔ settlement
  • full gate green locally: typecheck, lint, format, 1220 tests

🤖 Generated with Claude Code

…s, and edits in place

The schedule modal becomes `ScheduleModal`, with a method: straight-line
as before; a custom curve, one amount per month of the range, spread
evenly until typed into and refused until it sums to the total; and a
loan, whose terms (principal, rate, draw date, day count, payment
frequency and amount, interest-only, maturity, capitalized interest, the
note and current-portion accounts) the server turns into the amortization
table, with the entry's legs chosen by how the payment settles (accrued
and paid through the bank feed, or booked by the schedule). Preview runs
the server's generator without keeping it and shows the roll-forward
month by month, the overlap warnings and, on an edit, what the schedule
has posted and the path the change takes.

Edit schedule on the panel loads the definition back from the envelope's
mechanics (the facts' months and amount for a schedule stored before the
definition rode the envelope) and saves it through rebuild-schedule while
nothing has posted; once a month has posted the save is locked with the
server's reason. The schedule rendering reads a loan as the note's
balance moved by the period's interest rather than by the balance
difference, which is zero for an interest-only note. All of it goes
through the generated SDK (`@/lib/ledger/schedules`), against the
operations live since robosystems v1.20.0.

Claude-Session: https://claude.ai/code/session_01WaJSHXWM1Lnpdrf5P7wiYW
@github-actions github-actions Bot added the change:normal Normal change: automated review documented on the pull request before merge label Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Change classification: normal — platform or application code changed.

A normal change carries a documented automated review on this pull request before merge, in place of a second human approver. The change-classification job requests that review and passes once it has been posted. Recorded by the change-classification job.

@jfrench9

Copy link
Copy Markdown
Member Author

@claude please review this PR

@claude

claude Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Claude finished @jfrench9's task in 32s —— View job


Review of #524: schedule form with custom curves, loans, preview and in-place edit

  • Read the diff for schedules.ts, SchedulePanel, ScheduleRendering and content.tsx
  • Read ScheduleModal.tsx lines 1–700 (state, edit prefill, validation, definition builder, preview and save)
  • Not reviewed: ScheduleModal.tsx lines 700–1434 (form fields and preview rendering) and the test files. I did not run the test gate or the app.

Verdict: the structure is sound. Please fix the render loop below before merging.

Bug: custom curve can loop forever

ScheduleModal.tsx:383-393

Take method = Custom curve, with an original amount entered and no valid range. The default state has periodEnd = '', so months = [].

  • curve = [], so curve.length === months.length.
  • [].every(...) is true, so untouched is true. depreciableCents > 0 is also true.
  • The early return is skipped. The effect calls setForm with curve: months.map(() => ''), which is a fresh [].
  • curve changes identity, so the effect re-runs, and this repeats. React will throw "Maximum update depth exceeded".

It needs only a custom method, a typed original amount, and the end date still blank.

Fix: guard on months.length === 0 before the effect does anything, or compare contents instead of identity (skip the setForm when prefill equals curve). Add a test that picks Custom, types an original amount, and leaves the end date empty.

Smaller issues

  1. Rename failure after a successful rebuild (ScheduleModal.tsx:546-562). If rebuildSchedule succeeds and updateSchedule then fails, the user sees an error but the rebuild is already persisted. onSaved never fires, so the panel and sidebar stay stale. Retrying rebuilds again. Either call onSaved before surfacing the rename error, or say in the message that the definition was saved and only the rename failed.
  2. Edit only sends monthly_amount for straight-line (ScheduleModal.tsx:550). The PR says omitted fields keep their stored values. When a straight-line schedule is switched to Custom or Loan, the stale monthly_amount may survive on the server. Please confirm the server clears it when method changes, or send null explicitly.
  3. Custom curve needs an original amount (ScheduleModal.tsx:373-379). curveMatches requires originalCents > 0, but the asset details are collapsed behind showAssetDetails and the amount is only required here. Check that the UI makes this obvious, for example by auto-opening that section for Custom and showing why Save is disabled. A schedule loaded for edit with a curve but no original_amount can never be saved.
  4. mechanics! in ScheduleRendering.tsx. It is safe, because loanTerms is only non-null when mechanics is. Returning { mechanics, loanTerms } from one helper would avoid the assertion.
  5. Sparse factsFallback tie-break (schedules.ts, scheduleFactsFallback). The tie-break picks the larger amount. That is fine, but a comment would help a future reader.

Looks good

  • Everything goes through the generated SDK, with errors in the facade's "<label> failed: …" shape so friendlyError and extractDetail still apply.
  • Any form edit clears the preview, and preview is separate from save.
  • Saves are locked once months have posted, with the server's edit_warning shown.
  • A loan's rendering is driven by the interest fact rather than the balance difference, which fits an interest-only note.
  • NewScheduleModal is replaced and its tests are ported.
  • Tests are colocated, as the repo conventions ask.
    · branch feature/schedule-curves-and-editing

@jfrench9
jfrench9 merged commit 835d502 into main Oct 22, 2026
7 checks passed
@jfrench9
jfrench9 deleted the feature/schedule-curves-and-editing branch October 22, 2026 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:normal Normal change: automated review documented on the pull request before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant