feat(collab): surface Zulip chat in the Collaboration bullet end-to-end - #1466
Conversation
Zulip chat data reached `silver.class_collab_chat_activity` but stopped there: nothing carried it through gold → metric → API → UI. Three gaps, all fixed, plus an e2e test that drives the whole path. 1. Silver class. `class_collab_chat_activity` only declared a `-- depends_on` on the m365/slack chat models, so a `tag:zulip-proxy+` run never rebuilt it. Added the zulip ref. (`union_by_tag` already includes any tagged model whose table exists; this just wires the build-order edge so prod's connector-scoped run refreshes the class.) 2. Gold view. `insight.collab_bullet_rows` only branched on `insight_m365` / `insight_slack`. Added Branch 4b for `data_source = 'insight_zulip_proxy'`, emitting the single counter `zulip_messages_sent` (mirrors the m365 Teams branch). 3. Metric query_ref + catalog. The IC (…0012) / Team (…0005) collab bullet `query_ref`s enumerate every FE-visible `metric_key` via `sumIf` + `ARRAY JOIN`. New append-only migration `m20260624_000001_collab_zulip_chat` re-sets both with the zulip key (based on the latest `m20260604_000002` distribution shape); `m20260624_000002_seed_zulip_collab_catalog` adds the catalog row (`collab_bullet_rows.zulip_messages_sent`, "Zulip Messages", source_tags ["zulip-proxy"]). Both registered in migration/mod.rs. E2E: new `specs/collab_zulip_chat.test.yaml` (+ schemas/templates and zulip bronze placeholders) seeds alice/bob/carol chat (40/20/10, one Engineering team) with a duplicate `uniq` row and asserts the IC bullet returns `zulip_messages_sent` value=40, median=20, range[10,40] (dedup holds). Adding the key raises the collab bullet from 20 to 21 FE keys, so `collab_emails_sent.test.yaml`'s `size(items)` is bumped 20 -> 21. Full e2e suite: 33 passed. Scope: the secondary collab `query_ref`s — member heatmap (…0041) and department distribution (…0045) — carry their own copies of the key list and are intentionally left for a follow-up; the IC/Team bullet that backs the person-profile Collaboration card (and this test) is fully wired. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
|
Warning Review limit reached
More reviews will be available in 35 minutes and 18 seconds. Learn how PR review limits work. To continue reviewing without waiting, enable usage-based billing in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR adds ChangesZulip Collaboration Metric Pipeline
Sequence Diagram(s)sequenceDiagram
participant Airbyte
participant bronze_zulip_proxy
participant silver_class_collab_chat_activity
participant insight_collab_bullet_rows
participant analytics_api_migration
participant metric_catalog
Airbyte->>bronze_zulip_proxy: insert Zulip users and messages
bronze_zulip_proxy->>silver_class_collab_chat_activity: feed zulip_proxy__collab_chat_activity
silver_class_collab_chat_activity->>insight_collab_bullet_rows: emit zulip_messages_sent row
analytics_api_migration->>insight_collab_bullet_rows: update metrics.query_ref SQL
analytics_api_migration->>metric_catalog: seed catalog and threshold rows
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/backend/services/analytics-api/src/migration/m20260624_000002_seed_zulip_collab_catalog.rs`:
- Line 139: Run rustfmt on the Zulip collab seed migration so the backend
lint/test pipeline can pass; the current formatting still differs around the
tracing::info! call in the seed_zulip_collab_catalog migration and the adjacent
block referenced by the formatter. Reformat the affected statements in that
migration module to match rustfmt output, especially the section around the
SEEDS.len() log and the later lines noted by the formatter.
In `@src/ingestion/scripts/migrations/20260518000000_collab-bullet-rewrite.sql`:
- Around line 198-212: The Zulip branch in the migration reads from
silver.class_collab_chat_activity without deduplication, so it can overcount
versions from ReplacingMergeTree(unique_key). Update the Branch 4b SELECT to
read the FINALized view of that table or otherwise dedupe upstream before
aggregating, and keep the existing filter on data_source = 'insight_zulip_proxy'
while preserving the metric mapping in the Zulip branch.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8990147c-ed69-4b6c-90db-ab733c34d9ff
📒 Files selected for processing (11)
src/backend/services/analytics-api/src/migration/m20260624_000001_collab_zulip_chat.rssrc/backend/services/analytics-api/src/migration/m20260624_000002_seed_zulip_collab_catalog.rssrc/backend/services/analytics-api/src/migration/mod.rssrc/ingestion/scripts/create-bronze-placeholders.shsrc/ingestion/scripts/migrations/20260518000000_collab-bullet-rewrite.sqlsrc/ingestion/silver/collaboration/class_collab_chat_activity.sqlsrc/ingestion/tests/e2e/specs/collab_emails_sent.test.yamlsrc/ingestion/tests/e2e/specs/collab_zulip_chat.test.yamlsrc/ingestion/tests/e2e/specs/schemas/bronze_zulip_proxy.messages.yamlsrc/ingestion/tests/e2e/specs/schemas/bronze_zulip_proxy.users.yamlsrc/ingestion/tests/e2e/specs/templates/zulip.yaml
| -- ─── Branch 4b: class_collab_chat_activity — Zulip ─────────────────── | ||
| -- Single key (zulip_messages_sent), no ARRAY JOIN needed. Mirrors the | ||
| -- m365 Teams branch: one silver row per (person, date) where | ||
| -- data_source = 'insight_zulip_proxy' → period-summed chat messages. | ||
| SELECT | ||
| lower(z.email) AS person_id, | ||
| p.org_unit_id AS org_unit_id, | ||
| z.date AS metric_date, | ||
| 'zulip_messages_sent' AS metric_key, | ||
| toFloat64(ifNull(z.total_chat_messages, 0)) AS metric_value | ||
| FROM silver.class_collab_chat_activity AS z | ||
| LEFT JOIN insight.people AS p ON lower(z.email) = p.person_id | ||
| WHERE z.data_source = 'insight_zulip_proxy' | ||
| AND z.email IS NOT NULL | ||
| AND z.email != '' |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major
🧩 Analysis chain
🌐 Web query:
ClickHouse ReplacingMergeTree SELECT without FINAL duplicate rows before background merges
💡 Result:
In ClickHouse, the ReplacingMergeTree engine does not automatically deduplicate rows during a standard SELECT query [1][2]. The engine is designed for eventual consistency; deduplication is a background process that occurs only when ClickHouse decides to merge data parts [1][3]. If you perform a SELECT query without the FINAL modifier, ClickHouse will return all versions of the rows currently present in the table, including duplicates that have not yet been processed by a background merge [1][2][3]. To ensure you receive only deduplicated results (i.e., only the latest version of each unique record based on the ORDER BY key), you must use one of the following approaches: 1. Use the FINAL modifier: Adding FINAL to your SELECT query forces ClickHouse to perform the deduplication logic at query time [1][2]. While this ensures correctness, it carries a performance cost because it requires extra processing during the query [1][4][5]. 2. Use a Session/Query Setting: For convenience, you can enable the final setting at the session or profile level (e.g., SETTINGS final = 1), which applies this logic to all relevant queries without needing to rewrite every SQL statement [6]. 3. Query Optimization: If using FINAL, you can improve performance by using the setting do_not_merge_across_partitions_select_final=1, which allows partitions to be processed independently [1][6][5]. Additionally, filtering on columns present in the ORDER BY or partition key can significantly reduce the amount of data processed during a FINAL query [1][6][7]. 4. Alternative Query Patterns: For performance-critical applications, some users prefer to avoid FINAL entirely by using aggregation functions like argMax in a GROUP BY clause to manually select the latest version of records [8][7]. Because background merges are unpredictable, you should never rely on the table being fully deduplicated for query correctness unless you explicitly use FINAL or handle deduplication in your query logic [1][2][4].
Citations:
- 1: https://clickhouse.com/docs/guides/replacing-merge-tree
- 2: https://clickhouse.com/docs/engines/table-engines/mergetree-family/replacingmergetree
- 3: https://oneuptime.com/blog/post/2026-03-31-clickhouse-what-is-replacingmergetree/view
- 4: Best way to deduplicate identical rows ClickHouse/ClickHouse#64554
- 5: https://blog.duyet.net/2024/06/clickhouse-replacingmergetree/
- 6: https://altinity.com/blog/clickhouse-replacingmergetree-explained-the-good-the-bad-and-the-ugly
- 7: https://medium.com/@raphael3213/clickhouse-replacingmergetree-how-does-it-work-7d6bb3991851
- 8: https://stackoverflow.com/questions/65089234/clickhouse-replacingmergetree-doesnt-guarantee-absence-of-duplicates
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the migration and the source table definition(s)
git ls-files | rg '20260518000000_collab-bullet-rewrite\.sql|class_collab_chat_activity|class_collab|zulip|ReplacingMergeTree|unique_key|_version' -n
# Show the relevant section of the migration with line numbers
sed -n '180,235p' src/ingestion/scripts/migrations/20260518000000_collab-bullet-rewrite.sql
# Search for the silver table definition and any upstream dedup logic
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
'CREATE TABLE .*class_collab_chat_activity|class_collab_chat_activity|ReplacingMergeTree|FINAL|unique_key|_version|zulip_messages_sent|insight_zulip_proxy' .Repository: constructorfabric/insight
Length of output: 50382
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find any table DDL or transformation around the silver.class_collab_chat_activity source
fd -a 'class_collab_chat_activity' .Repository: constructorfabric/insight
Length of output: 248
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect relevant files and definitions with minimal output.
echo "=== migration snippet ==="
nl -ba src/ingestion/scripts/migrations/20260518000000_collab-bullet-rewrite.sql | sed -n '198,212p'
echo
echo "=== matching table definitions / references ==="
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
'CREATE TABLE .*class_collab_chat_activity|ENGINE = ReplacingMergeTree|class_collab_chat_activity|zulip_messages_sent|insight_zulip_proxy|unique_key|_version|FINAL' \
src . | sed -n '1,220p'Repository: constructorfabric/insight
Length of output: 229
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== migration excerpt ==="
sed -n '198,212p' src/ingestion/scripts/migrations/20260518000000_collab-bullet-rewrite.sql
echo
echo "=== silver table definition ==="
sed -n '1,220p' /home/jailuser/git/src/ingestion/silver/collaboration/class_collab_chat_activity.sql
echo
echo "=== related joins / dedup patterns ==="
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
'FROM silver\.class_collab_chat_activity|FINAL|ReplacingMergeTree|unique_key|zulip_messages_sent|insight_zulip_proxy' \
src/ingestion | sed -n '1,220p'Repository: constructorfabric/insight
Length of output: 28140
Read the Zulip branch with FINAL or dedupe upstream.
silver.class_collab_chat_activity is a ReplacingMergeTree(_version) table keyed by unique_key, so a plain SELECT can surface duplicate versions before merges and overcount zulip_messages_sent.
Proposed fix
-FROM silver.class_collab_chat_activity AS z
+FROM silver.class_collab_chat_activity AS z FINAL📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| -- ─── Branch 4b: class_collab_chat_activity — Zulip ─────────────────── | |
| -- Single key (zulip_messages_sent), no ARRAY JOIN needed. Mirrors the | |
| -- m365 Teams branch: one silver row per (person, date) where | |
| -- data_source = 'insight_zulip_proxy' → period-summed chat messages. | |
| SELECT | |
| lower(z.email) AS person_id, | |
| p.org_unit_id AS org_unit_id, | |
| z.date AS metric_date, | |
| 'zulip_messages_sent' AS metric_key, | |
| toFloat64(ifNull(z.total_chat_messages, 0)) AS metric_value | |
| FROM silver.class_collab_chat_activity AS z | |
| LEFT JOIN insight.people AS p ON lower(z.email) = p.person_id | |
| WHERE z.data_source = 'insight_zulip_proxy' | |
| AND z.email IS NOT NULL | |
| AND z.email != '' | |
| -- ─── Branch 4b: class_collab_chat_activity — Zulip ─────────────────── | |
| -- Single key (zulip_messages_sent), no ARRAY JOIN needed. Mirrors the | |
| -- m365 Teams branch: one silver row per (person, date) where | |
| -- data_source = 'insight_zulip_proxy' → period-summed chat messages. | |
| SELECT | |
| lower(z.email) AS person_id, | |
| p.org_unit_id AS org_unit_id, | |
| z.date AS metric_date, | |
| 'zulip_messages_sent' AS metric_key, | |
| toFloat64(ifNull(z.total_chat_messages, 0)) AS metric_value | |
| FROM silver.class_collab_chat_activity AS z FINAL | |
| LEFT JOIN insight.people AS p ON lower(z.email) = p.person_id | |
| WHERE z.data_source = 'insight_zulip_proxy' | |
| AND z.email IS NOT NULL | |
| AND z.email != '' |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/ingestion/scripts/migrations/20260518000000_collab-bullet-rewrite.sql`
around lines 198 - 212, The Zulip branch in the migration reads from
silver.class_collab_chat_activity without deduplication, so it can overcount
versions from ReplacingMergeTree(unique_key). Update the Branch 4b SELECT to
read the FINALized view of that table or otherwise dedupe upstream before
aggregating, and keep the existing filter on data_source = 'insight_zulip_proxy'
while preserving the metric mapping in the Zulip branch.
…facing chain Captures what this PR's zulip work had to discover by hand so the next connector reaches the UI first try: - /connector create.md §3.6c: a connector that only reaches silver does NOT appear in the UI — union_by_tag folds it into silver.class_<X> and nothing else is automatic. Documents the 4 layers to wire (silver depends_on edge, gold <section>_bullet_rows branch on data_source='insight_<snake>', the bullet query_ref sumIf+ARRAY JOIN — noting a section has several copies: IC/Team/member …0041/dept …0045 — and the metric_catalog row), with file locations and PR constructorfabric#1466 as the worked example. - /connector validate.md: new "Dashboard metric surfacing" checklist mirroring the chain. - /metric-e2e-test SKILL.md: rig gotchas that cost real time — adding a bronze table for a not-yet-seeded connector (placeholder heredoc + schema yaml, else the seeder fails on system.columns); stale cargo-target volume serving a binary without new SeaORM migrations (confirm via seaql_migrations, fix with down -v); 1045 access-denied from stale .env; querying the still-up CH/MariaDB after a run; and that adding a metric_key to a shared section shifts other tests' size(items). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
…issed `build()` builds analytics-api into the `cargo-target` Docker volume. On Docker Desktop (macOS) the mtimes cargo reads through the bind mount do not reliably advance when sources change on the host, so cargo relinked a stale cached object and the spawned binary silently lacked newly-added SeaORM migrations. Symptom: a freshly-added metric/query_ref/catalog migration never ran (absent from `seaql_migrations`), so `./e2e.sh test` failed with NO_ZULIP / `size(items)` off-by-one and only a full `down -v` cold rebuild (itself flaky — the volume is often "in use") worked around it. Fix: touch the analytics-api crate sources before `cargo build`, forcing a recompile of that crate every run (~1-2 min; it is a leaf bin, so its deps stay cached — not a cold build). `./e2e.sh test` now picks up new migrations with no `down -v` ritual. Confirmed: full suite 33 passed on a warm volume. Also updates the /metric-e2e-test skill note (the stale-binary trap is now auto-handled; `down -v` is only for a DB/disk reset). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cypilot/.core/skills/connector/workflows/create.md`:
- Around line 450-458: The fenced diagram block in the workflow docs is
unlabeled, triggering the markdown lint rule. Update the fenced block around the
bronze_<snake> to analytics-api flow to specify a language tag on the opening
fence, and keep the rest of the content unchanged so the documentation still
renders correctly while satisfying MD040.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ddb406a9-f1dc-412c-ba13-80cc855a7ae0
📒 Files selected for processing (4)
.claude/skills/metric-e2e-test/SKILL.mdcypilot/.core/skills/connector/workflows/create.mdcypilot/.core/skills/connector/workflows/validate.mdsrc/ingestion/tests/e2e/e2e_lib/analytics_api.py
✅ Files skipped from review due to trivial changes (2)
- cypilot/.core/skills/connector/workflows/validate.md
- .claude/skills/metric-e2e-test/SKILL.md
| ``` | ||
| bronze_<snake>.<stream> | ||
| → <snake>__<class>.sql (silver, tag silver:class_<X>) ← §3.5 | ||
| → silver.class_<X> (union_by_tag — AUTOMATIC) | ||
| → insight.<section>_bullet_rows (gold VIEW, CH migration) ← layer A | ||
| → metrics.query_ref (…IC/Team/member/dept) (SeaORM) ← layer B | ||
| → metric_catalog row (label/threshold, SeaORM) ← layer C | ||
| → analytics-api → person-profile <Section> card (UI) | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language to the fenced code block.
Line 450 uses an unlabeled fenced block (MD040), which can break docs lint.
Suggested fix
-```
+```text
bronze_<snake>.<stream>
→ <snake>__<class>.sql (silver, tag silver:class_<X>) ← §3.5
→ silver.class_<X> (union_by_tag — AUTOMATIC)
→ insight.<section>_bullet_rows (gold VIEW, CH migration) ← layer A
→ metrics.query_ref (…IC/Team/member/dept) (SeaORM) ← layer B
→ metric_catalog row (label/threshold, SeaORM) ← layer C
→ analytics-api → person-profile <Section> card (UI)</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 450-450: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cypilot/.core/skills/connector/workflows/create.md` around lines 450 - 458,
The fenced diagram block in the workflow docs is unlabeled, triggering the
markdown lint rule. Update the fenced block around the bronze_<snake> to
analytics-api flow to specify a language tag on the opening fence, and keep the
rest of the content unchanged so the documentation still renders correctly while
satisfying MD040.
Source: Linters/SAST tools
cargo fmt --check flagged line-wrapping in m20260624_000002_seed_zulip_collab_catalog (tracing::info! args + assert! chain). No logic change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
| unique_key: zulip-msg-bob-20260105 | ||
| count: 20 | ||
|
|
||
| - $ref: templates/zulip.yaml#/templates/carol_message |
There was a problem hiding this comment.
Is carol in the same team as Alice and Bob?
There was a problem hiding this comment.
yes
and Carol is their manager )
Problem
Zulip chat data reached
silver.class_collab_chat_activity(viaunion_by_tag) but stopped at silver — nothing carried it through gold → metric → API → UI, so it never appeared on the person-profile Collaboration card. Three downstream gaps, all fixed here, plus an e2e test that drives the whole path.Changes
1. Silver class — build-order edge
class_collab_chat_activity.sqlonly declared-- depends_onfor the m365/slack chat models, so atag:zulip-proxy+run never rebuilt it. Added the zulip ref. (union_by_tagalready includes any tagged model whose table exists; this wires the graph edge so the connector-scoped prod run refreshes the class.)2. Gold view — Zulip branch
20260518000000_collab-bullet-rewrite.sql:insight.collab_bullet_rowsonly branched oninsight_m365/insight_slack. Added Branch 4b fordata_source = 'insight_zulip_proxy'→ single counterzulip_messages_sent(mirrors the m365 Teams branch). (IdempotentDROP+CREATEview, no migration tracking table → edited the canonical definition in place.)3. Metric
query_ref+ catalogThe IC (
…0012) / Team (…0005) collab bulletquery_refs enumerate every FE-visiblemetric_keyviasumIf+ARRAY JOIN. New append-only SeaORM migrations:m20260624_000001_collab_zulip_chat— re-sets both query_refs with the zulip key (based on the latestm20260604_000002distribution shape).m20260624_000002_seed_zulip_collab_catalog— catalog rowcollab_bullet_rows.zulip_messages_sent("Zulip Messages",source_tags ["zulip-proxy"]).Both registered in
migration/mod.rs.E2E test
specs/collab_zulip_chat.test.yaml(+ schemas/templates, + zulip bronze placeholders increate-bronze-placeholders.sh) seeds alice/bob/carol chat (40/20/10, one Engineering team) with a duplicateuniqrow, and asserts the IC bullet returnszulip_messages_sentvalue=40, median=20, range[10,40] (dedup holds). Adding the key raises the collab bullet 20 → 21 FE keys, socollab_emails_sent.test.yaml'ssize(items)is bumped 20 → 21.Full e2e suite: 33 passed.
Scope
The secondary collab
query_refs — member heatmap (…0041) and department distribution (…0045) — carry their own copies of the key list and are intentionally left for a follow-up. The IC/Team bullet that backs the person-profile Collaboration card (and this test) is fully wired.🤖 Generated with Claude Code
Summary by CodeRabbit