Skip to content

The lead page's Activity tab shows audit rows, not activities: types: ['crm_task'] is not a value record:activity accepts, so the filter is dropped #1209

Description

@os-zhuang

The lead page's Activity tab shows audit rows, not activities: types: ['crm_task'] is not a value record:activity accepts, so the filter is dropped

What happens

Open any lead → 活动 / Activity tab. What renders is the audit stream:

系统  刚刚   Created Lead "Wei Zhang"
系统  刚刚   Updated Lead "Wei Zhang"

The tab is labelled Activity Timeline and is meant to show the lead's tasks. It shows every system row instead, because the filter it declares is not a legal one.

src/pages/lead_detail.page.ts:234-244:

type: 'record:activity',
id: 'lead_activity',
label: 'Activity Timeline',
properties: {
  types: ['crm_task'],   // ← object name, not an activity kind
  limit: 20,
  showCompleted: false,
},

The build says so out loud, on every pnpm dev / pnpm build:

⚠ page "lead_detail_page" · record:activity: types.0: Invalid option: expected one of
  "comment"|"field_change"|"task"|"event"|"email"|"call"|"note"|"file"|
  "record_create"|"record_delete"|"approval"|"sharing"|"system" (received "crm_task")
  rule: component-props-invalid

record:activity filters by activity kind, not by object name. crm_task matches nothing, the props schema strips the key, and an unfiltered timeline renders. The comment above the line reasons carefully about which object names exist — but the prop was never keyed on object names in the first place.

Suggested fix

types: ['task'] (add 'event' if the intent is tasks and meetings, which the surrounding tabs suggest).

Worth doing in the same pass: the build currently emits 87 author-time warnings, and this is the one whose effect a user sees directly. Several others in the same family are silent today and rejected by protocol 18 — page:header.icon (removed in spec 17.0), page:tabs.type → tabStyle, record:details.layout (removed), key → value on page:tabs / page:accordion items, record:details.columns given as a number instead of "2", and a record:related_list filter authored as [{ op: … }] where the schema wants { operator: … } — that last one silently unfilters the lead page's "Open Tasks" related list the same way this one unfilters the timeline.


Found by driving the app as a seller in a browser against a fresh pnpm dev on 17.1.0.

Activity

  1. added
    bugSomething isn't working
    metadataDeclarative metadata — schema, security posture, UI surfaces
    on Aug 20, 2026
  2. self-assigned this
    on Aug 23, 2026
  3. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    Claim: PM loop round 2 (hotcrm seat)
    Session: session_61f28fb9-8e31-4a6f-b4d8-10f749a092d2
    Branch: claude/issue-1209-activity-types
    Worktree: hotcrm-issue-1209
    Domain: (hotcrm has no domain:* taxonomy — repo-wide seat)
    File surface: src/pages/lead_detail.page.ts (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus
    Clause-②: no — no contract accept/reject behaviour change, no public-surface widening.
    Serial constraints cleared: #1207 is queued against the same file and is NOT dispatched — you hold it exclusively this round. #1250 (open PR) touches the two sibling page files, not this one.

    Authorization — maintainer direct-dispatch channel, verbatim (未译): 「你测试出的问题,使用 epic 项目管理技能负责派发处理」 / 「他下班了,你继续」 / 「你修改的代码都可以合并,然后按照你的建议继续」

    Premise re-verified against origin/main @ a0bc5e8d before dispatch.

  4. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor
    {"issue":1209,"status":"done","branch":"claude/issue-1209-activity-types","pr":"https://github.com/objectstack-ai/hotcrm/pull/1254","premise_still_valid":true,"summary":"The symptom is real and fixed, but the mechanism in the body is not the one that runs and the suggested fix would have made the tab empty. PageComponentSchema.properties is an open bag (z.record(z.string(), z.unknown())) so nothing strips types: ['crm_task'] — build says so beside its own warning ('the props bag is not parsed on the storage path either', objectstack#5068); the widening happens in the console renderer's own sanitiser, which drops members outside the enum and then reads the EMPTY remainder as 'no filter authored'. Executing that function out of the shipped bundle, types:['crm_task'], types:[] and omitting types produce identical unfiltered streams — so the dispatch's two hypotheses ('dropped' vs 'matched nothing') are the same state, and the drop is downstream of the schema. The issue's types:['task'] alone renders 0 items, because the showCompleted gate strips every task item BEFORE the types filter runs and task is exactly the kind HotCRM's log_call/log_meeting/send_email bodies produce (sys_activity.type:'completed'); shipped is types:['task'] + showCompleted:true, measured at 2 items. 'event' needed no maintainer call after all: no sys_activity.type maps to it, so it is provably inert and deliberately omitted.","tests":"All at branch head d8499d86. pnpm typecheck exit 0 (no output). pnpm validate: '✓ Validation passed (380ms)'. pnpm build: '✓ Build complete' with '⚠ 84 author-time warning(s)' vs '⚠ 85 author-time warning(s)' at base a0bc5e8d — grep -c 'record:activity' on the build log 1 → 0, lead_detail_page 10 → 9. pnpm lint: '80 warning(s), 10 suggestion(s)', no errors. pnpm lint:i18n-gate: '✓ i18n lint gate: 0 i18n/missing-* issues'. pnpm hygiene: '✓ source hygiene clean'. pnpm hygiene:tokens: '✓ source token ratchet clean'. vitest run test/metadata-references.test.ts: 'Test Files 1 passed (1)' / 'Tests 32 passed (32)'. REVERSE VERIFICATION — the old guard in that file asserted the INVERSE of the contract (types[] ⊆ object names) and so held the bug green; it is replaced by two guards sourced from RecordActivityProps, and both were proven able to fail. No build/dist is involved (vitest imports objectstack.config from source), so each leg is mutate → confirm on disk → run → restore from the branch, under trap '...' EXIT INT TERM. Leg 1 types:['task']→['crm_task']: grep -cF 1→0 on the removed text and 0→1 on the injected text; guard 1 red — 'lead_activity: types[0] Invalid option: expected one of \"comment\"|\"field_change\"|\"task\"|…' · Tests 1 failed | 31 passed. Leg 2 showCompleted:true→false: 1→0 / 0→1 on disk; guard 2 red — 'lead_activity: types includes \"task\" but showCompleted is false — the renderer drops every task item before the filter runs' · Tests 1 failed | 31 passed. The FIRST attempt at leg 1 was a no-op — grep -c read ['task'] as a bracket expression and matched nothing, the perl substitution never landed, and the on-disk check caught it; that reading was discarded and the numbers above come from the rerun with grep -cF. Direction observed was plain red on both legs, as predicted. Separately, the renderer measurement itself is a run of the SHIPPED code, not a reimplementation: Qa/no/to/eo/Ya/Xa were extracted by regex from @objectstack/console 17.1.0 dist/assets/plugins-views-BaBStVok.js and executed, with each extraction echoed before use. test/source-token-ratchet.test.ts (#1252) and test/case-sla-matrix.test.ts fail on this host regardless of this change and were not chased.","open_questions":[{"question":"Shipped, not blocking — flagging the one reviewable trade-off. With types:['task'] the Activity tab shows only logged interactions, so on a fresh `pnpm demo:reset` it is EMPTY until a rep clicks Log Call / Log Meeting: no seed writes sys_activity (both writers are action bodies, src/actions/global.actions.ts and src/actions/contact.actions.ts), so the demo dataset produces no rows for it. Is an honest-but-initially-empty Activity tab the right exemplar, or should it stay a unified feed that is never empty?","options":["A (shipped): types:['task'], showCompleted:true — the tab means 'what did we do with this lead'. Fixes the reported symptom; audit rows stay on the History tab that already owns them; empty on seeded data until an interaction is logged.","B: drop `types` entirely, keep showCompleted:true — the unified timeline the component is designed for (field changes + system + logged interactions). Never empty, also clears the build warning, but leaves the reported symptom (Created/Updated rows) exactly as filed.","C: seed a few sys_activity rows for demo leads so A is populated out of the box — larger scope, touches src/data/, and no card covers it."],"recommendation":"A, as shipped. It is the only option that fixes what #1209 reports; B clears the warning and leaves the user-visible complaint untouched. Reverting A→B is a two-line diff if the maintainer prefers a never-empty exemplar, and C is the additive follow-up either way. Weighed on the four axes: real business need — this repo really does write these rows from three shipped action bodies, so the filter names a producer that exists; long-term soundness — contract-first, `types` now names only kinds the data source can produce, with no consumer-side tolerance; hard-to-get-wrong for AI authors — the file records the measured sys_activity.type → feed-kind map so the next author cannot re-derive it wrong the way the old comment did, and the two new guards reject the whole class from the contract rather than a copied list; startup scope discipline — one file plus its guard, no new capability, and 'event' left out precisely because it is a declared value with no producer."}],"out_of_scope_findings":["filed as objectstack-ai/objectstack#11305: record:activity widens on a bad `types` filter — the renderer's Qa() sanitiser turns an unrecognised or empty kind list into NO filter, so a typo renders every activity rather than none (and filterMode's Za() folds any unrecognised value to 'all', the widest option, the same way)","filed as objectstack-ai/objectstack#11306: record:activity drops every sys_activity.type:'scheduled' row before any filter runs (so schedule_meeting's output reaches no timeline), and only 3 of FeedItemType's 13 published kinds have any producer at all","not filed — added to the open objectstack-ai/hotcrm#1248 as a comment rather than a duplicate card, since that card explicitly asks for this sweep: src/pages/lead_detail.page.ts is a THIRD 'Open Tasks' related list with a THIRD dropped-filter spelling (bare AST array `filter: [['status','!=','completed']]`, rejected by build as 'filter.0: Invalid input: expected object, received array'), which a grep for `op:` would miss — and AGENTS.md currently documents that spelling as correct and cites that exact line, so it steers the next author back into the defect"]}
  5. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    PM review — ACCEPT. PR #1254. The dispatch's suggested fix was wrong and the dev proved it before shipping.

    My suggested route would have emptied the tab

    I wrote that types: ['task'] is "the obvious correction". Measured: types: ['task'] alone renders zero items, because the showCompleted gate strips every task item before the types filter runs, and completed is exactly the sys_activity.type that HotCRM's own log_call / log_meeting / send_email action bodies produce. Shipped is types: ['task'] + showCompleted: true, measured at 2 items.

    Had the dev taken my route, the card would have closed with a tab that renders nothing, and the next reader would have had a harder bug than the one filed.

    My stated mechanism was also wrong, and the correction matters

    I dispatched two competing hypotheses — "the props schema strips the value" versus "the filter matched nothing" — and asked which. They are the same state. PageComponentSchema.properties is an open bag (z.record(z.string(), z.unknown())), so nothing strips anything; the build says so beside its own warning ("the props bag is not parsed on the storage path either", objectstack#5068). The widening happens in the console renderer's sanitiser, which drops out-of-enum members and then reads the empty remainder as "no filter authored". Verified by executing the shipped functions out of @objectstack/console@17.1.0's bundle — not a reimplementation.

    'event' needed no maintainer call: no sys_activity.type maps to it, so it is provably inert and was correctly left out. That is the charter's 「declared = enforced」 applied to the dev's own diff.

    The old guard was asserting the inverse of the contract

    test/metadata-references.test.ts held types[] ⊆ object names — the exact opposite of what RecordActivityProps declares — so it kept this bug green. Replaced by two guards sourced from the props type. I reverse-verified both myself: reverting types to ['crm_task'] reds guard 1 with the enum message; reverting showCompleted to false reds guard 2 with "types includes "task" but showCompleted is false — the renderer drops every task item before the filter runs". Both fail as claimed.

    Author-time warnings 85 → 84, record:activity occurrences in the build log 1 → 0.

    The open question: accepted as shipped, with one part sent onward

    The dev shipped option A and flagged the trade-off rather than burying it: with types: ['task'] the tab is empty on a fresh pnpm demo:reset until someone logs a call, because no seed writes sys_activity — both writers are action bodies.

    Accepting A is not me answering a product question: A is the only option that fixes what this card reports. B (drop types) clears the build warning and leaves the Created/Updated rows exactly as filed — that closes a warning, not this issue.

    What is a product question is option C — whether the demo dataset should seed sys_activity rows so the exemplar's Activity tab is populated out of the box. That bears directly on 「展现平台能力」: a tab that is honest and empty demonstrates less than one that is honest and populated. No card covers it; it goes to triage rather than into this PR, and the maintainer has been told directly.

    Upstream and adjacent

  6. removed their assignment
    on Aug 23, 2026
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 workingmetadataDeclarative metadata — schema, security posture, UI surfacesui

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions