Repository navigation
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
Activity
- addedbugSomething isn't workingSomething isn't workingmetadataDeclarative metadata — schema, security posture, UI surfacesDeclarative metadata — schema, security posture, UI surfaces
on Aug 20, 2026 - added a commit that references this issue
on Aug 23, 2026 - addedpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatch
on Aug 23, 2026 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 nodomain:*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@a0bc5e8dbefore dispatch.- added a commit that references this issue
on Aug 23, 2026 {"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"]}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 theshowCompletedgate strips every task item before the types filter runs, andcompletedis exactly thesys_activity.typethat HotCRM's ownlog_call/log_meeting/send_emailaction bodies produce. Shipped istypes: ['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.propertiesis 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: nosys_activity.typemaps 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.tsheldtypes[] ⊆ object names— the exact opposite of whatRecordActivityPropsdeclares — so it kept this bug green. Replaced by two guards sourced from the props type. I reverse-verified both myself: revertingtypesto['crm_task']reds guard 1 with the enum message; revertingshowCompletedtofalsereds 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:activityoccurrences 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 freshpnpm demo:resetuntil someone logs a call, because no seed writessys_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_activityrows 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
- objectstack#11305:
record:activitywidens on a badtypesfilter — a typo renders every activity instead of none, andfilterModefolds unrecognised values to'all', the widest option, the same way. Fail-open on a filter is the platform-side root cause of this card. - objectstack#11306: every
sys_activity.type: 'scheduled'row is dropped before any filter runs, and only 3 ofFeedItemType's 13 published kinds have any producer. - Added to The Open Tasks related lists never filter out completed tasks:
op/neqare not the spellingsrecord:related_listaccepts #1248 as a comment rather than a duplicate:lead_detail.page.tscarries a third dropped-filter spelling (bare AST array) that a grep forop:would miss — andAGENTS.mddocuments that spelling as correct while citing that exact line. Filed asAGENTS.mddocuments the broken related-list filter spelling as correct, cites a rejected line as the example, and forbids fixing it #1257; it is the largest thing this round produced, because it is the instruction file manufacturing the class.
- objectstack#11305:
- removedpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatch
on Aug 23, 2026 - added a commit that references this issue
on Aug 24, 2026 - added a commit that references this issue
on Aug 24, 2026
The lead page's Activity tab shows audit rows, not activities:
types: ['crm_task']is not a valuerecord:activityaccepts, so the filter is droppedWhat happens
Open any lead → 活动 / Activity tab. What renders is the audit stream:
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:The build says so out loud, on every
pnpm dev/pnpm build:record:activityfilters by activity kind, not by object name.crm_taskmatches 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→valueonpage:tabs/page:accordionitems,record:details.columnsgiven as a number instead of"2", and arecord:related_listfilter 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 devon17.1.0.