Skip to content

Browser pass #2058 — HotCRM-side defects (18 items: accepted quote still takes lines, half-converted leads, agents losing their cases, unreachable Sales Home, …) — list card, split per item at dispatch #2060

Open

Description

@objectstack-fleet

Filing gate: ① product defects with reach measured, the HotCRM-side findings of the maintainer's browser pass #2058. They were measured in a browser on main 1d7148bf (HotCRM 4.0.1, @objectstack/* 17.7.0) by three cloud testers, and each was re-checked by the repo:hotcrm seat against its screenshot. Filed by the repo:hotcrm seat, session_012zh91QzFgePbkmuHnugLN3.

Who acts on it: the repo:hotcrm seat. This is a list card: each item is split into its own card or dispatched on its own when it is picked up, as #2029 was. The items are folded here because one seat fire may file at most three cards.

Evidence: every item names its finding id. Reports and screenshots are on the never-merged evidence branches claude/qa-browser-pass-a|b|c, under qa/2026-10-10-browser-pass/<a|b|c>/: REPORT.md holds the steps, the API answers and the server-log lines, and each finding's PNG sits beside it.

Not here:

The items

p2: a business outcome is wrong

  1. A quote the customer accepted still takes new line items, and its totals stop matching its lines (B-09).
    • QTE-0007 was accepted with subtotal 19,000. A second line was added from Related → New (POST crm_quote_line_item 200). The quote still says 19,000 / 17,100 while its lines sum to 24,000; the drafted contract says 17,100.
    • Owner: src/revenue/objects/quote_line_item.hook.ts. quote_total_rollup skips settled quotes, but nothing refuses a line write on an accepted or expired parent.
    • Expected: content/docs/sales/quotes.mdx:120, "the quote is frozen".
  2. Lead conversion with "Create Opportunity" ticked and no amount leaves a half-converted lead (A-01).
    • The screen's Opportunity Amount is optional (src/sales/flows/lead-conversion.flow.ts:133), while crm_opportunity.amount is required.
    • Submit fails at create_opportunity ("Amount is required") after the account and contact were created and the "New contact" toast fired. The lead stays new.
    • Re-running with an amount recovers.
  3. A service agent who creates or escalates a Critical case is shown "Record not found" right after "Case created successfully" (B-04).
    • The case is escalated and reassigned to the service-manager pool. The agent's crm_case read scope is own (src/sales/profiles/service-agent.profile.ts:25), and no sharing rule keeps the creator or previous owner on it (src/service/sharing/case.sharing.ts).
  4. An agent cannot claim an anonymous web case as-is (B-03).
    • The agent form makes Account required (src/service/views/case.view.ts:424), web cases carry none, and crm_case.crm_account is optional in metadata.
    • Saving In Progress fails: "Please check the highlighted fields: Account".
    • content/docs/service/cases.mdx:148 says "Move an unowned case to In Progress … and it becomes yours".
  5. Every persona lands on Executive Overview, and Sales Home, the App Launcher and the Utility Bar have no UI path (C-07, B-19, C-14, A-22).
  6. An ownerless lead makes lead_assignment fail (A-08, C-20).

p3: the outcome is right, but the way there is broken or misleading

  1. Form required flags that block a default the server would supply (A-04, A-21, B-11, C-08, A-20).
    • The default lead form requires Lead Owner, though insert stamps the creator (src/sales/views/lead.view.ts:293-296).
    • Line item unit_price is required, so the list-price default is unreachable (src/revenue/objects/opportunity_line_item.object.ts:145).
    • Quote expiration_date is required, so the +30-day default is unreachable (src/revenue/objects/quote.object.ts:171-176).
    • Forecast period_end and snapshot_date are required although the hook derives them (src/sales/objects/forecast.object.ts:106,137).
    • Opportunity stage has no field defaultValue (src/sales/objects/opportunity.object.ts:227).
  2. Actions offered to personas who cannot complete them (C-12, C-13).
    • A sales rep is offered Escalate / Close Case, which end in "Node 'escalate' failed: update_record(crm_case) failed: You do not have permission" (src/service/actions/case.actions.ts visible).
    • A rep is offered Enroll Members, which enrols nobody. For any user, the flow ends with no result message (src/marketing/actions/campaign.actions.ts:28, src/marketing/flows/campaign-enrollment.flow.ts).
  3. Every contact ends up "primary" (A-15). Conversion into an existing account stamps is_primary: true (src/sales/flows/lead-conversion.flow.ts:473), and Mark as Primary never demotes the others (src/sales/actions/contact.actions.ts:26). The demo's Primary Contacts view lists 11 of 11.
  4. A reader's Helpful vote refreshes the article's Last Reviewed date (B-17). See src/service/objects/knowledge_article.hook.ts:119, and content/docs/service/knowledge-base.mdx:63, which says the date "reflects real attention".
  5. All Forecasts groups periods alphabetically ("Sep 2026 → Q4 2026 → Q4 2025 …") (C-09). It groups by the text period_label desc (src/sales/views/forecast.view.ts:39).
  6. Refusal copy:
    • The campaign date error names API fields: "Campaign start_date … must not be after end_date" (C-11, src/marketing/objects/campaign.hook.ts:84).
    • "2 open opportunity(ies), 0 active quote(s)" (A-17, src/sales/objects/contact.hook.ts:191).
    • The product delete refusal counts quote lines as quotes (B-16, src/revenue/objects/product.hook.ts:70).
  7. Locales:
    • Approval flow and step names stay English in zh-CN / ja-JP / es-ES, e.g. "Large Deal Approval (on create) / Sales Manager Review" (C-15). The packs' flows: blocks carry only screen flows (src/sales/translations/zh-CN/app.ts:604).
    • zh-CN labels both the Activity group and the Events item 活动 (C-16, src/sales/translations/zh-CN/app.ts:30-31).
  8. Public forms have no label, so visitors see the heading crm_lead / crm_case (A-09, B-08). Give web_to_lead (src/sales/views/lead.view.ts:893) and web_to_case (src/service/views/case.view.ts:452) a label; the console fallback goes upstream.
  9. "Accounts by Location" has nothing to plot (A-13): every seeded account has office_location: null (src/sales/data/sales.seed.ts).
  10. docs/feature-inventory.md and two docs pages describe the app as it was (A-12, B-18, C-21):
    • ACC-006 lists a renewals tab;
    • ACC-004 says "open opportunities";
    • SVC-001 lists signature, rating and feedback fields;
    • SVC-007 says escalation does not reassign;
    • KB-005 / KB-006 cite view_count;
    • ADM-001 lists 7 groups;
    • ACT-012, PRM-001 (7 sets, not 6), PRM-006 (10 rules, not 9) and ADM-010 (38 cases, not 8);
    • content/docs/service/knowledge-base.mdx:33 says "most-viewed first".
  11. Measure first, because the testers disagree: imports through a shipped mapping (A-11 against C's ADM-004).
    • Tester A got 400 MAPPING_FORMAT_MISMATCH ("declares sourceFormat "csv" but the payload is "json"") for both crm_lead_import and crm_account_import.
    • Tester C imported 2 accounts through "Account Import (CSV)" with 200.
    • Find which path reaches which, then file the defect on the right side.
  12. After the pin bump (chore(deps): upgrade @objectstack/* past 17.7.0 once the next release publishes — it carries the platform fixes #2016, #2000, #1583 and #1585 wait on #2031), re-point the public-form links (A-07, B-07, C-03).

Acceptance

Each item is closed by its own PR, or recorded here as not a defect with the reason. The split card or PR names the finding id.

Duplicate check

Every hotcrm issue and PR was listed (/issues?state=all, 1,896 rows) and matched case-insensitively on each item's dedupe words:

No open card covers any item.


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 10, 2026
  2. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round R81 (#2060 item 1 only)
    Session: session_01WnMDsLSaUQUD41WaWnADcF
    Account: os-warren (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-2060-item-1-accepted-quote-lines
    Worktree: hotcrm-issue-2060
    Domain: repo:hotcrm (single-lane repo, no domain:* taxonomy)
    Seat: repo:hotcrm#1
    File surface: item 1 only (B-09, an accepted or expired quote still takes line writes): src/revenue/objects/quote_line_item.hook.ts and, only if the measured producer is there, the quote's own freeze guard in src/revenue/objects/; content/docs/sales/quotes.mdx (+ its zh locales) only if a sentence there becomes false; their tests; one .changeset/2060-*.md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default (dispatch-gates --tier --repo objectstack-ai/hotcrm src/revenue/objects/quote_line_item.hook.ts answers "The tier stays the PM's per-card judgment call"; no declared tier glob is hit)
    Clause-②: no
    Responsibility: the app: nothing refuses a line write whose parent quote is accepted or expired (quote_line_item.hook.ts skips the rollup for a settled quote but writes the line) | none: no platform path freezes a child of a settled record | every rep or manager who adds or edits a line on an accepted quote (measured in #2058 group B, QTE-0007)
    Thread-read: none
    Serial constraints cleared: src/revenue/** has no open PR and no other claim; #2069 (quote tax rollup) is queued behind this item. In flight this round, disjoint files: #2067 (src/sales account approval) and #2068 (src/sales/flows/lead-conversion.flow.ts). Partial landing: the PR says Refs #2060 (item 1), ⛔ not Fixes; on merge the card goes back to pm:queue with a Release: line.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 2060,
    "status": "needs_decision",
    "branch": "claude/issue-2060-item-1-accepted-quote-lines",
    "pr": "#2076",
    "session": "session_01WnMDsLSaUQUD41WaWnADcF (subagent run: the dispatching seat's session)",
    "premise_still_valid": true,
    "summary": "Premise confirmed on origin/main 1d7148b (17.7.0): a sales rep's insert onto an accepted quote was admitted, the quote kept its accepted subtotal (100) and its lines summed to 800. A rep's line update was admitted too. Landed in draft PR #2076 (head a4d0170): quote_workflow now names ['crm_quote', 'crm_quote_line_item'] and refuses a user's line insert or update whose parent quote (old or new) is accepted or expired, with RECORD_LOCKED/409. The refusal reads 'Quote QTE-n - Name is accepted, so its line items can no longer be added or changed.' It keeps the quote freeze's own user/system boundary, and an update is judged by what it changes, so the documented 4.0.0 line-number back-fill touch still numbers lines on settled quotes. The suggested route was a hook on the line's own before* events; I landed in the existing freeze hook instead, for two measured reasons. First, it is one freeze with one envelope. Second, the standalone hook measured about 346 tokens against 219 of src/revenue headroom. Line DELETE is deliberately not guarded, and that half needs a decision (open_questions). The engine cascades a user's quote or account delete to the lines as that user (measured), and no declared hook-context key separates that cascade from a hand delete. A delete guard therefore makes every settled quote with lines, and the account holding it, undeletable (ablation 2b measured it). No platform parent-lock exists to route to: controlled_by_parent gates access only, and the approvals lock is per record and per approval. The card assignee was not written (it is os-warren). Labels ci/cd, metadata and backend were applied by the repo labeler, not by me.",
    "tests": "Final union at a4d0170 (PR head): bash os-verify-lock.sh -c 'pnpm verify' -> 'VERDICT command-exit 0 · held the lock 594s'. validate '✓ Validation passed'; typecheck exit 0 (tsconfig includes test/**); lint --strict 18 suggestion(s), no errors; lint:i18n-gate '✓ i18n lint gate: 0 i18n/missing-* issues'; hygiene '✓ source hygiene clean'; hygiene:tokens '✓ source token ratchet clean'; build '✓ Build complete'; test 'Test Files 178 passed (178) / Tests 3645 passed | 1 skipped (3646)'. The first union, at b928c9c, was VERDICT command-exit 1: registration-list-completeness had 2 failures because its hook classifier required a string object; that is fixed in a4d0170. New suite test/quote-line-freeze.test.ts has 10 tests, asserting code+status RECORD_LOCKED/409, the wording, and no record id in the message. Refused: rep add on accepted and on expired, rep edit, manager edit, move off and move onto a settled quote, and the shipped QuickJS path. Let through: draft control with the rollup subtotal at 350, a system seed write, the back-fill touch, and a manager deleting an accepted quote with lines. Ablations used objectstack scripts/ablation-replace.mjs, each landing with an anchor 1->0 hit and a blob change, each restored with 'blob == HEAD (3c35e0b8859c) and git diff HEAD is empty'. A1: object back to 'crm_quote' gave 'Tests 5 failed | 5 passed (10)'; all 5 in-process refusals went red with 'the write went through'. A2 first try was NULL (adding beforeDelete alone leaves changed=false on delete input) and its all-green reading is void. A2b: beforeDelete plus changed forced on delete gave 'Tests 1 failed | 9 passed'; only the cascade pin went red, with 'Quote QTE-0017 - Freeze Quote 11 is accepted, so its line items can no longer be added or changed.' A3 (crm_case in the list): NOT MEASURED for the test guard; the platform's defineStack StackCrossReferenceError fired first. No dist/ is involved: the tests import src through objectstack.config. hygiene:tokens for src/revenue at base 1d7148b: business ~15,641/16,000, total ~18,781/19,000. At a4d0170: business ~15,846, total ~18,986 (headroom ~14). CI snapshot at report time: Check Changeset success, link-check success; Build and Test, Quality Checks, Playwright and CodeQL in_progress.",
    "mcp_calls": "0 — no MCP tool used; reads were gh api REST GETs (card, comments, PR, check-runs)",
    "api_writes": "2 relay strokes (fleet-write dispatch: POST /repos/objectstack-ai/objectstack/dispatches, run as objectstack-fleet[bot]). (1) pr_create: POST /repos/objectstack-ai/hotcrm/pulls (draft) + POST /repos//issues/2076/assignees ['os-warren'], read back byte-identical (8436 bytes). (2) this os-dev-report comment: POST /repos//issues/2060/comments. label-write: 0. git push: 3 (not REST).",
    "gates": [
    "pnpm verify @ a4d0170: VERDICT command-exit 0 (validate, typecheck, lint --strict, lint:i18n-gate, hygiene, hygiene:tokens, build, test: 178 files / 3645 passed / 1 skipped)",
    "pnpm verify @ b928c9c: VERDICT command-exit 1 (registration-list-completeness, 2 tests); fixed in a4d0170",
    "CI @ a4d0170 at report time: Check Changeset success, link-check success, Label Pull Request success; Build and Test (22.x), Quality Checks, Playwright, Analyze Code in_progress"
    ],
    "line_budget": {
    "additions": 257,
    "deletions": 12,
    "src_additions": 43,
    "src_deletions": 3,
    "hygiene_tokens_src_revenue": "business semantics ~15,641 -> ~15,846 (ceiling 16,000); authored total ~18,781 -> ~18,986 (ceiling 19,000, headroom ~219 -> ~14)"
    },
    "files_changed": [
    ".changeset/2060-item-1-accepted-quote-lines.md",
    "src/revenue/objects/quote.hook.ts",
    "test/quote-line-freeze.test.ts",
    "test/helpers/verify-stack.ts",
    "test/package-ownership.test.ts",
    "test/refusal-envelope.test.ts",
    "test/registration-list-completeness.test.ts"
    ],
    "deviations": [
    "Suggested route (line's own before* hooks, insert/update/delete) changed. The landing is the existing quote_workflow freeze, made a two-object hook, guarding insert and update. Measured reasons: one freeze and one envelope, and the standalone hook did not fit the src/revenue token ceiling (about 346 tokens vs 219 of headroom). This sits in the claim's 'quote's own freeze guard' surface.",
    "Line delete is deliberately not guarded, pending a decision (open_questions). The measured cascade cost is in the summary and in ablation 2b.",
    "Test files beyond the new suite, all consequences of the two-object hook or the new refusal site: verify-stack runShippedHook takes an optional object; package-ownership judges every name in a list instead of skipping it; registration-list-completeness accepts a list (it went red otherwise); refusal-envelope site count 23 -> 24.",
    "Docs not edited: no sentence in quotes.mdx became false. quotes.mdx 'How prices are calculated' was already untrue for settled quotes; after this PR it is untrue only for line removal, which waits on the decision.",
    "The first pnpm verify (b928c9c) was red. It waited 8m13s behind a sibling's verify; the fix landed in a4d0170 and verify re-ran green on that head."
    ],
    "open_questions": [
    {
    "question": "#2060 item 1, delete half: should a user be able to remove a line from an accepted or expired quote? Measured on 17.7.0: a line delete is admitted, the quote's totals keep the accepted figure, and the lines stop matching them; the same pre-existing gap this PR leaves unchanged. The constraint: the engine deletes a quote's lines AS THE DELETING USER when that user deletes the quote, or deletes an account that cascades to it (crm_quote.crm_account is master-detail cascade). Each line's beforeDelete sees that user inside the cascade transaction. On 17.7.0 no declared hook-context key tells that cascade from a hand delete; referentialFieldClear exists only for the set_null cleanup update.",
    "options": [
    "A. Keep what PR #2076 ships. Single-line deletes on a settled quote stay open, and quotes.mdx gets one sentence saying a line can still be removed and the totals then keep the accepted figure. Effort: a docs line in 3 locales. Business: removing a line from an accepted quote has no real use, so the residual hole is cheap but real. Long-term: code and doc's 'frozen' disagree on one action. AI-safety: an agent reading 'frozen' will assume lines are immutable, so the doc sentence must say otherwise. Startup: adds nothing.",
    "B. Refuse every user line delete on a settled quote. Effort: about 2 lines plus a test flip. Cost, measured by ablation 2b: a manager's or admin's delete of ANY accepted or expired quote with lines is refused with the line message, and so is the delete of any prospect account holding one. That is the #720 class (a frozen record making other records undeletable), which the #720 ruling turned away for set_null cleanups. Expired quotes are produced nightly, so ordinary cleanup breaks. Business: breaks a real need. Long-term: blunt, with collateral. AI-safety: teaches 'guard delete' with a hidden cascade cost. Startup: no new surface.",
    "C. Refuse a HAND delete only and let a parent's cascade through. This needs a declared HookContext key for a cascade delete: the cascade-delete twin of referentialFieldClear, set on each child delete that cascadeDeleteRelations issues and carried across the QuickJS sandbox. Filed upstream in objectstack with this app as the named consumer; the delete half here WAITS for it (AGENTS.md §2), with no sniffing of ctx.transaction or __ keys (the #1165 ruling). Effort: platform spec, engine and sandbox, then about 2 lines here. Business: serves both the freeze and real cleanup. Long-term: contract-first, symmetric with an existing declared key. AI-safety: a declared signal removes the sniffing trap. Startup: one new declared platform key, pulled by a real consumer.",
    "D. Freeze deletion of the settled quote itself (a quote beforeDelete refusal), so its lines never cascade. Effort: about 5 lines plus tests. Cost: the same account-delete blockage as B, at the quote level with a clearer message. Business: forbids cleanup of expired quotes. Startup: no new surface."
    ],
    "recommendation": "C, with A as the interim state (it is what PR #2076 ships, plus the docs sentence). On the business axis, B and D break a measured, routine cleanup path to close a hole nobody needs open. On long-term soundness and AI-safety, C is the contract-first answer and the only one that removes the temptation to sniff an undeclared signal. On the startup axis, C's new key has a pulling consumer and mirrors a key the platform already declared. If the maintainer judges the residual hole not worth a platform key, A alone is the right answer, not B or D."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: the engine write door as a sales rep (the path REST POST /api/v1/data/crm_opportunity_line_item takes), measured on 17.7.0 at hotcrm a4d0170: the rep's insert of a 3 x 500 line onto their own closed_won deal was admitted, and the deal amount stayed 1,000 while its lines sum to 1,500 · evidence: opportunity_amount_rollup skips closed deals; opportunity_lifecycle freezes a closed deal's fields but nothing guards its lines; content/docs/sales/opportunities.mdx:40 'Closed deals are locked' and :116 'The sum of all line items rolls up into the opportunity Amount' · sibling of #2060 item 1 (B-09), same family; the seat decides whether it joins #2060's list or gets its own card. Its delete half meets the same cascade question as open_questions[0] · dedupe words: closed deal line item, opportunity_line_item closed_won, opportunity_amount_rollup, Closed deals are locked",
    "carrier: the follow-up dispatch of #2060 item 1's delete half · content/docs/sales/quotes.mdx 'How prices are calculated' ('Anything you change in the line items … the totals update automatically') stays untrue for removing a line from a settled quote until that decision lands · noted, not filed"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Verdict: os-dev report on PR #2076 (#2060 item 1) — ACCEPT the insert/update half; the delete half goes to decision card #2078. Not governed (7 files: src/revenue/, test/, .changeset/; +257 / −12). The seat lands it through the merge queue once every check on head a4d01701 is green. repo:hotcrm seat, session_01WnMDsLSaUQUD41WaWnADcF, 2026-10-12T10:04Z.

    Checked against GitHub and the branch (fetched at a4d0170; 2 commits), not taken from the report:

    • PR shape: draft, base main, assignee os-warren. The first line is Refs #2060 (item 1), and the body carries no closing keyword next to any card number. Clause-②: no starts a line.
    • The fix:
      • quote_workflow now names ['crm_quote', 'crm_quote_line_item'] (still beforeInsert / beforeUpdate).
      • A user's line write whose old or new parent quote is accepted or expired throws RECORD_LOCKED / 409 with business wording and no record id.
      • The guard runs only for a write that changes something beyond id / updated_at / updated_by / line_number, and only with a user, so the 4.0.0 line-number back-fill and system writes pass.
      • The route differs from the seat's suggestion (a separate hook on the line). The reasons are measured and accepted: one freeze, one envelope, and the standalone hook did not fit the src/revenue token ceiling (~346 tokens needed, 219 free).
    • Tests:
      • test/quote-line-freeze.test.ts (10 cases) asserts code + status, the wording, refusals on add / edit / move on / move off / the shipped QuickJS path, and five let-through controls.
      • The four shared test edits are mechanical consequences of a hook naming a list: package-ownership, registration-list-completeness, refusal-envelope 23 → 24, and the runShippedHook object override.
      • Ablation A1 (object back to crm_quote) turned all 5 in-process refusals red.
    • Changeset, read sentence by sentence: it states the refusal, its exact message, that system writes and the back-fill are unaffected, and that removing a line is still allowed pending a decision.
    • CI on a4d0170 at this reading: 8 of 9 checks success; Build and Test (22.x) in_progress.

    Deviations, accepted: the route change above; delete deliberately unguarded (see below); the first pnpm verify at b928c9c was red on registration-list-completeness, fixed in a4d0170 and re-run green (VERDICT command-exit 0).

    The delete half (the report's needs_decision): filed as #2078 (needs-user-decision). Options B and D are excluded by the maintainer's ruling on #720 (a frozen record must not make accounts undeletable). The seat recommends C (a platform-declared cascade signal, HotCRM waits) with A's doc sentence as the interim. This card is not blocked by it: items 2–18 stay dispatchable.

    Out-of-scope findings:

    ⚠️ src/revenue token ceiling: after this PR the authored total reads ~18,986 / 19,000 (~14 tokens of headroom). #2069 and #2077 both land in src/revenue and cannot fit without an equal shrink. Their dispatches will say so.

    On merge: Refs, so this card stays open. The seat moves it back to pm:queue with a Release: line naming item 1 as landed.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: session session_01WnMDsLSaUQUD41WaWnADcF · reason: item 1 landed partially (insert/update half) · destination: pm:queue for items 2–18; item 1's delete half waits on #2078

    Landed: PR #2076 MERGED as b8f5421 on main through the merge queue. repo:hotcrm seat, 2026-10-12T10:07Z.


    Generated by Claude Code

  6. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Oct 12, 2026
  7. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 12, 2026
  8. 62 remaining items

  9. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round R87 (#2060 item 5 only; serial dispatch per the maintainer's 「当前任务处理完,合并后改为串行派发」)
    Session: session_01P8mZuzuyZAHSrgQKmv6ASv
    Account: sunhaolin (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-2060-item-5-persona-home
    Worktree: hotcrm-issue-2060-item-5
    Domain: repo:hotcrm (single-lane repo, no domain:* taxonomy)
    Seat: repo:hotcrm#1
    File surface: item 5 only (C-07, B-19, C-14, A-22: every persona lands on Executive Overview; Sales Home, the App Launcher and the Utility Bar have no UI path; a service agent's home and nav show revenue tiles and entries it cannot read): src/sales/apps/crm.app.ts (navigation: the landing entry and gates on the dashboard / report entries); src/service/index.ts (navigationContributions: the service persona's landing and its own entries' gates); src/sales/pages/home.page.ts only if it becomes a landing; the test that pins the app's navigation (or a sibling); content/docs/sales/activities*.mdx and any other docs sentence that becomes false; one .changeset/2060-item-5-*.md. ⛔ Not deleting app_launcher_page / utility_bar_page (that half comes back as a recommendation for the maintainer); ⛔ not profiles, permission sets or sharing (no grant changes) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default (dispatch-gates --tier --repo objectstack-ai/hotcrm: "no path-derived mandate")
    Clause-②: no
    Responsibility: the app: one Home entry (executive_dashboard) for every persona, and dashboard / report nav entries carry no read gate | partly: whether the console offers a per-persona landing is measured first (H2) | every persona at sign-in, most visibly the service agent who lands on refused revenue widgets
    Thread-read: 6183901714
    Serial constraints cleared: serial dispatch; nothing else of this seat is in flight now that PR #2116 (#2060 item 4) has landed (bd5fda21). src/sales/apps/crm.app.ts was last changed by #2087 (branding); no writer in flight. Partial landing: the PR says Part of #2060, ⛔ not Fixes; on merge the card goes back to pm:queue.


    Generated by Claude Code

  10. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 15, 2026
  11. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 2060,
    "status": "done",
    "branch": "claude/issue-2060-item-5-persona-home",
    "pr": "#2117",
    "session": "session_01P8mZuzuyZAHSrgQKmv6ASv (subagent run: the dispatching seat's session)",
    "premise_still_valid": true,
    "summary": "Item 5 only. What landed: the nav half and the Sales Home half. The landing half is NOT fixed. On base bd5fda2 (18.0.0-next.1) every persona opens the app on Executive Overview, and service.agent / service.manager get 4x403 on it plus refused Sales Performance (17x403), CRM Overview (8x403), Pipeline Coverage (1x403) and Sales Activity (1x403, 1 of 13 tiles). Lead Inflow is not refused (it is a crm_lead report and every set reads leads), so H1's list was one entry wrong. Fix in src/sales/apps/crm.app.ts: Home, Sales Performance, CRM Overview and Pipeline Coverage, plus a new pinned nav_sales_home (sales_home_page, second after Home, the page's first UI path), carry visible: current_user.can("crm_opportunity", "read"). The service personas' sidebar goes from 21 entries to 17. Admin, manager and reps keep all 27 and gain Sales Home (all 10 targets render, 0 refusals). H3 measured: nav requiredPermissions is enforced server-side only as CAPABILITY names. requiredPermissions ['crm_opportunity:read'] stripped the entry for every persona, admin included (build warns capability-reference-unknown), and the personas hold no capabilities, so a server-enforced gate needs a grant change, which the claim excludes. H2 measured: the console's landing is resolveLandingRoute -> findFirstRoute over the served nav, and it ignores visible and the object-read prune, so service.agent still lands on Executive Overview, now an entry its own sidebar hides. The spec's PageTypeSchema 'home' ('use home for app-level landing') and isDefault on a non-record page are not honoured either. Both are platform findings below. Per-persona landing and the App Launcher / Utility Bar fate are open_questions for the maintainer. Docs fixed in 3 locales: activities (no longer 'the page you land on'), quick-tour (two pinned entries; which five are hidden without opportunity read), dashboards (CRM Overview audience).",
    "tests": "All on a tree byte-identical to HEAD 97d9ef6 (the changeset was on disk during the runs and committed afterwards with no edits). pnpm test (locked): 'Test Files 181 passed (181)', 'Tests 3690 passed | 1 skipped (3691)', VERDICT command-exit 0. Targeted, before the full run: vitest run test/app-navigation-shape.test.ts test/docs-quick-tour-navigation.test.ts test/docs-sales-index-navigation.test.ts test/docs-analytics-vocabulary.test.ts test/metadata-references.test.ts test/docs-dashboard-tiles.test.ts test/docs-view-rosters.test.ts: '7 passed (7)', '172 passed (172)'. New pin, test/app-navigation-shape.test.ts 'entries built on opportunities are gated on reading them': exact gated set of 5, gate source string, Sales Home second after Home, and the permission sets parting rep/manager (read) from service agent/manager (no read). Ablation, once, no file kept, through objectstack scripts/ablation-replace.mjs (wrap mode, trap restore). Removing 'visible: READS_OPPORTUNITIES' from nav_crm_dashboard: anchor x1 -> x0, blob 35d70620df70 -> 7d2788396b63. 'x exactly these entries carry the opportunity read gate' failed with 'expected [ nav_home, nav_sales_home, ...(2) ] to deeply equal [ ...(3) ]'. Restored: blob == HEAD 35d70620df70 and 'git diff HEAD' empty. No build/dist leg: the test imports src directly. Browser: Playwright (executablePath /opt/pw-browsers/chromium) against my server on 4165, fresh DB plus pnpm demo:staff, DOM text probes and 403 counting on /api/ responses, 6 personas before and after (tables in the PR body). Sidebar click into Sales Home measured as na.rep.",
    "gates": "Local, exit captured before any pipe: pnpm validate exit 0 ('Validation passed'); pnpm typecheck exit 0; pnpm lint exit 0 ('18 suggestion(s)'); pnpm lint:i18n-gate exit 0 ('i18n lint gate: 0 i18n/missing-* issues'); pnpm hygiene exit 0 ('source hygiene clean'); pnpm hygiene:tokens exit 0 ('source token ratchet clean'); pnpm build exit 0 ('Build complete', 8 author-time warnings, 8 on base too); pnpm test exit 0. apps/docs pnpm run types:check exit 0 ('Types generated successfully'). Declared narrowing: apps/docs 'next build' was not run locally; it is CI's 'Typecheck and Build'. Heavy runs went through os-verify-lock with OS_VERIFY_LOCK_SLOT=hotcrm-2060, every VERDICT command-exit 0, waited 0s. dispatch-gates.mjs does not answer hotcrm, so this list was derived by hand from package.json verify and ci.yml. PR #2117 check-runs on 97d9ef6 when reported: Vercel Preview Comments success, Label Pull Request success, Quality Checks success, Playwright success, link-check success, Typecheck and Build success, Check Changeset success, Build and Test (22.x) in_progress, Analyze Code (javascript) in_progress.",
    "line_budget": "+106 / -24 = 130 changed lines across 16 files (budget about 150): crm.app.ts +39/-4, app-navigation-shape.test.ts +36/-3, docs 9 files +21/-15, locale packs 3 x +1, docs-quick-tour-navigation.test.ts +2/-2, changeset +5",
    "files_changed": [
    ".changeset/2060-item-5-persona-home.md",
    "content/docs/analytics/dashboards.mdx",
    "content/docs/analytics/dashboards.zh-Hans.mdx",
    "content/docs/analytics/dashboards.zh-Hant.mdx",
    "content/docs/getting-started/quick-tour.mdx",
    "content/docs/getting-started/quick-tour.zh-Hans.mdx",
    "content/docs/getting-started/quick-tour.zh-Hant.mdx",
    "content/docs/sales/activities.mdx",
    "content/docs/sales/activities.zh-Hans.mdx",
    "content/docs/sales/activities.zh-Hant.mdx",
    "src/sales/apps/crm.app.ts",
    "src/sales/translations/es-ES/app.ts",
    "src/sales/translations/ja-JP/app.ts",
    "src/sales/translations/zh-CN/app.ts",
    "test/app-navigation-shape.test.ts",
    "test/docs-quick-tour-navigation.test.ts"
    ],
    "deviations": [
    "File surface widened by three lines: src/sales/translations/{zh-CN,es-ES,ja-JP}/app.ts each gain nav_sales_home's label (the wording the pack already gives sales_home_page). This is required by the AGENTS.md i18n checklist for a new nav entry and is named in the PR body. No profile, permission set or sharing file was touched.",
    "Gate route: visible (client CEL, current_user.can) instead of the preferred server-enforced requiredPermissions. Measured reason: the server reads requiredPermissions only as capability names, the object:verb form hid the entry for everyone, and a capability would be a grant change.",
    "PM fallback not achievable: 'gate the entries so the first one a persona sees is the right one' does not move the landing, because the console's landing ignores visible (measured). The per-persona landing is NOT delivered. It is open_questions[0].",
    "src/service/index.ts was not touched: no service-module entry needed a gate (every member that sees the app reads crm_case), and no landing could be contributed.",
    "The full local gate run happened before the changeset commit, on a tree byte-identical to the final HEAD 97d9ef6 (no edit between them).",
    "Ablation used objectstack's scripts/ablation-replace.mjs against the hotcrm worktree (hotcrm has no copy)."
    ],
    "mcp_calls": "0",
    "api_writes": "2 - (1) POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay, pr_create -> POST /repos/objectstack-ai/hotcrm/pulls, PR #2117, draft, assignee sunhaolin; read-back 9682 bytes identical); (2) POST /repos//issues/2060/comments (this os-dev-report, via post-stamped.mjs). git push x3 (not REST). label-write: 0.",
    "open_questions": [
    {
    "question": "Per-persona landing (rep -> Sales Home, manager/exec -> Executive Overview, service agent -> the service dashboard). On 18.0.0-next.1 no landing mechanism can express this inside this claim: the console lands on the first SERVED entry and ignores visible, and the only server-side gate (requiredPermissions) takes capabilities that no persona holds. Which route?",
    "options": [
    "A. Wait for the platform: objectui's landing (resolveLandingRoute / findFirstRoute) applies the sidebar's own gates (visible, the object-read prune). This PR's gates then move the service personas off Executive Overview automatically, to Leads, their first visible entry. Per-persona homes become a small HotCRM PR that orders the entries, gating with visible on positions or current_user.can, with the service module contributing its dashboard into an app-declared top group. Cost here: 0 now, roughly 40 lines later. Blocked on the objectui finding below.",
    "B. Server-enforced now: declare capabilities (defineCapability) per landing audience and grant them in systemPermissions on sales_rep / sales_manager / system_admin / the service sets. Gate nav_home / nav_sales_home with requiredPermissions, and have the service module contribute its home into a new top group. Cost: a grant change on 4-6 permission sets, new capabilities, a new group and tests, roughly 150-250 lines, and capabilities whose only meaning is the order of the sidebar.",
    "C. Platform feature: a per-item landing marker (the spec's own homePageId tombstone names navigation[].landing as the shape if the capability returns), designed enforce-first. This is new platform surface and the largest cost.",
    "D. One landing for everyone: move Sales Home first. Reps match the activities docs, but managers and admins lose Executive Overview, quick-tour needs a rewrite in 3 locales, and agents still land on refused tiles. Not recommended."
    ],
    "recommendation": "A. Business need: measured, the agent's landing is 4x403 and the docs promise reps Sales Home. A fixes the agent with no further HotCRM change and makes the rep landing declarable. Long-term: a console that lands a member on an entry its own sidebar hides is a platform defect, and fixing it at the producer is contract-first. B builds a lasting workaround (capabilities standing in for a landing concept) and C adds surface. AI-mistake: under A, visible means one thing everywhere. B teaches 'use capabilities to order the sidebar', and C adds a key that must ship enforce-first. Startup focus: A adds no surface. B adds capabilities with no pull beyond nav, and C adds a key. If the maintainer wants the agent off Executive Overview before objectui ships, B is the only route that works on 18.0.0-next.1."
    },
    {
    "question": "App Launcher (app_launcher_page, type 'app') and Utility Bar (utility_bar_page, type 'utility'): give a path, retire, or leave? Measured as na.rep at the typed URL. App Launcher renders inline: a header, a search input and an app:launcher 'Your apps' strip with 1 app (HotCRM), which duplicates the console's own app switcher and /home 'Your apps'. Utility Bar renders inline in the main column (position static, not floating): a heading, an empty Quick Notes card (the KNOWN_EMPTY exemption in test/metadata-references.test.ts), a search input and a notifications block that draws nothing. The console has no shell slot for either. No 'Unknown component'. No doc names either page as a destination.",
    "options": [
    "Give a path: two sidebar rows, for a page that duplicates the console's switcher and a 'floating' bar that renders as an ordinary page.",
    "Retire: delete both pages, their entries in the four locale packs (pages.app_launcher_page / pages.utility_bar_page) and the KNOWN_EMPTY exemption. One small follow-up card.",
    "Leave: keep both, reachable only by typed URL."
    ],
    "recommendation": "Retire. Business need: zero pull, measured. There is no nav entry, no doc destination and no consumer, App Launcher duplicates the console, and Utility Bar's floating tools never render. Long-term: a page whose type promises a shell role the console does not give is a false affordance. If the platform ships a utility-bar slot, re-author against it then. AI-mistake: HotCRM is the exemplar app, so an AI reading it learns that type 'utility' makes a floating bar and type 'app' makes a launcher, and both are wrong. Startup focus: shipped zero-consumer surface gets no sunk-cost exemption, so retire it now with no staged window. Giving a path adds two useless rows; leaving it keeps a wrong exemplar."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: public door. As service.agent on PR #2117's branch, /_console/ redirects to /apps/crm_enterprise/dashboard/executive_dashboard, whose nav entry's visible evaluates false (the sidebar omits it): 8 no-access texts, 4x403 on POST /api/v1/analytics/dataset/query. Named producer: HotCRM crm.app.ts nav_home. · evidence: objectui b9814b334 (= @objectstack/console 18.0.0-next.1) packages/app-shell/src/console/AppContent.tsx, resolveLandingRoute -> findFirstRoute(activeApp.navigation), walks the served tree without evaluateVisibility or withoutUnreadableObjectEntries, the two gates UnifiedSidebar applies before drawing. Upstream: objectui. The landing half of #2060 item 5 is Blocked-by this. · dedupe: resolveLandingRoute, findFirstRoute visible, landing hidden nav entry, withoutUnreadableObjectEntries landing",
    "class: c · Seam: spec:PageTypeSchema 'home' ("platform-level landing page ... Use home for app-level landing") / 'utility' ("Floating utility panel") -> renderer: objectui PageRenderer HomePageLayout / UtilityPageLayout (an inline page, with only the max-width differing); spec:PageSchema.isDefault on a non-record page -> consumer: none. · reach: named producer HotCRM src/sales/pages/home.page.ts (type 'home' + isDefault: true, served with isDefault true by /api/v1/meta/page/sales_home_page) and utility_bar.page.ts (type 'utility'). pnpm validate exits 0 with no word on either; the landing is the first nav item, and utility_bar_page renders in the main column, position static. The spec contradicts itself: app.zod.ts HOME_PAGE_ID_RETIRED says 'an app's landing page IS its first navigation item'. Upstream: objectstack spec (+ objectui renderer). · dedupe: PageTypeSchema home landing, utility floating panel, isDefault non-record page, UtilityPageLayout",
    "class: a · reach: public door. As na.rep, clicking sidebar Sales Home (pinned by PR #2117) shows the 'Quick Create' card rendering a 28-link copy of the app navigation (Home, Sales Home, SALES, Leads, ...) and no create action. · evidence: src/sales/pages/home.page.ts card quick_create holds { type: 'nav:menu', properties: {} }. HotCRM-side; outside this claim (home.page.ts only 'if it becomes a landing'), not edited. · dedupe: Quick Create nav:menu, sales_home_page quick_create, Sales Home Quick Create",
    "carrier: none (承接者:无) · noted, not filed: objectui UnifiedSidebar checkPerm accepts an object:verb form for nav requiredPermissions, but the server (holdsRequiredPermissions, capabilities only) strips such an entry first, so for nav that branch never runs. Recorded in PR Acceptance notes."
    ]
    }

  12. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report (rework 1)
    {
    "issue": 2060,
    "status": "done",
    "branch": "claude/issue-2060-item-5-persona-home",
    "pr": "#2117",
    "session": "session_01P8mZuzuyZAHSrgQKmv6ASv (subagent run: the dispatching seat's session)",
    "premise_still_valid": true,
    "summary": "Rework 1, as the seat asked (findings[2] of the first report): Sales Home's 'Quick Create' card is removed in this PR. Measured first: ComponentPropsMap on 18.0.0-next.1 has no create-record block. The action:* blocks run script / url / modal / flow / api / form; objectui's runner sends 'form' to a FormView at /forms/NAME, and a string 'modal' target may only name a page. A 'New Lead' button would therefore hard-code a console route, which crm.app.ts's own Inbox comment refuses, so option 2 applies. The card (a page:card around a bare nav:menu that drew 28 sidebar links) is gone, and so is its pages.sales_home_page.components.quick_create key in en / es-ES / ja-JP / zh-CN (create_menu had no pack key). A comment in the Recent Items style says why it is gone and what brings it back. The now-empty left_sidebar region stays: measured, an empty region renders nothing (RegionContent returns null; no data-region node, no column). No test, doc or KNOWN_EMPTY exemption named the card. One extra correction, outside the named surface and required by the made-false rule: the page description 'Sales team home page with key metrics and quick actions' is drawn under the page title and became false, so it, its aria text and the four packs now read 'key metrics, my open work and upcoming events'. Re-measured as na.rep on cab1440 after a sidebar click into Sales Home: 0 'Quick Create' texts, 0 nav:menu blocks, cards Key Performance Indicators / Ask the AI Assistant / Upcoming Events, 0 refused API calls, new description shown. Everything the seat accepted from the first round is unchanged. The two open_questions stay with the maintainer, and nothing was acted on them. The landing half of item 5 remains unfixed, as reported in the first round.",
    "tests": "On final head cab1440, through os-verify-lock (OS_VERIFY_LOCK_SLOT=hotcrm-2060): pnpm test gave 'Test Files 181 passed (181)', 'Tests 3690 passed | 1 skipped (3691)', VERDICT command-exit 0. The same run on the intermediate head 5ef6b46 had the same counts. Browser (Playwright, executablePath /opt/pw-browsers/chromium; my server on 4165, PID group killed after use; demo:staff seeded): na.rep clicks sidebar 'Sales Home', and the DOM probe on [data-page-type] shows description 'Sales team home page with key metrics, my open work and upcoming events', 'Quick Create' occurrences 0, [data-obj-type=nav:menu] 0, page:card titles [Key Performance Indicators, Ask the AI Assistant, Upcoming Events], 0 responses at or above 400 on /api/. Region measure: data-region nodes header / main / right_sidebar only; empty left_sidebar draws nothing. No ablation this round: no tested behaviour changed (a card was removed; no test pins it). First-round ablation evidence stands (on 97d9ef6).",
    "gates": "Local, on final head cab1440, exit captured before any pipe: pnpm validate exit 0 ('Validation passed'); pnpm typecheck exit 0; pnpm lint exit 0 ('18 suggestion(s)'); pnpm lint:i18n-gate exit 0 ('i18n lint gate: 0 i18n/missing-* issues'); pnpm hygiene exit 0 ('source hygiene clean'); pnpm hygiene:tokens exit 0 ('source token ratchet clean'); pnpm build exit 0 ('Build complete', 8 author-time warnings, same as base); pnpm test exit 0; apps/docs pnpm run types:check exit 0 ('Types generated successfully'). Heavy runs went through os-verify-lock, every VERDICT command-exit 0. Declared narrowing, as before: apps/docs next build is CI's. PR #2117 check-runs on cab1440 when reported: CodeQL success, Vercel Preview Comments success, Quality Checks success, Check Changeset success, link-check success, Analyze Code (javascript) success, Typecheck and Build success, Playwright success, Label Pull Request success, Build and Test (22.x) in_progress.",
    "line_budget": "Rework: +19 / -28 = 47 changed lines (asked: about 30). The card plus its comment is 28 lines in home.page.ts, the 4 pack keys 4, the changeset 2. The description correction (+6 / -7 = 13) is what took it over: home.page.ts description + aria + docblock bullet, and 4 pack descriptions. Whole PR vs base bd5fda2: +124 / -51 = 175 changed lines across 18 files (asked: about 160).",
    "files_changed": [
    ".changeset/2060-item-5-persona-home.md",
    "content/docs/analytics/dashboards.mdx",
    "content/docs/analytics/dashboards.zh-Hans.mdx",
    "content/docs/analytics/dashboards.zh-Hant.mdx",
    "content/docs/getting-started/quick-tour.mdx",
    "content/docs/getting-started/quick-tour.zh-Hans.mdx",
    "content/docs/getting-started/quick-tour.zh-Hant.mdx",
    "content/docs/sales/activities.mdx",
    "content/docs/sales/activities.zh-Hans.mdx",
    "content/docs/sales/activities.zh-Hant.mdx",
    "src/sales/apps/crm.app.ts",
    "src/sales/pages/home.page.ts",
    "src/sales/translations/en/app.ts",
    "src/sales/translations/es-ES/app.ts",
    "src/sales/translations/ja-JP/app.ts",
    "src/sales/translations/zh-CN/app.ts",
    "test/app-navigation-shape.test.ts",
    "test/docs-quick-tour-navigation.test.ts"
    ],
    "deviations": [
    "Rework surface widened past the seat's grant: the Sales Home page description (home.page.ts description + ariaDescribedBy + one docblock bullet, and pages.sales_home_page.description in the 4 packs). It promised 'quick actions' that removing the card made false, and the made-false rule binds. Named in the PR body.",
    "Rework line count 47 against about 30, and the PR total 175 against about 160; the overrun is that description correction.",
    "The left_sidebar region was kept, not removed: measured, an empty region renders nothing.",
    "The PR body edit needed --ack-through=6184497523. post-stamped refused at first because the Vercel preview comment was newer than the unstamped body; it carries no request, and the body now ends with a 2026-10-15T05:04Z stamp line saying so.",
    "First-round deviations stand unchanged (translations widening for nav_sales_home, the visible route, landing not delivered, gate-timing note, the cross-repo ablation tool)."
    ],
    "mcp_calls": "0",
    "api_writes": "Rework: 2. (1) PATCH /repos//issues/2117 (PR body, via post-stamped.mjs --body=2117 through the fleet relay, POST /repos/objectstack-ai/objectstack/dispatches; read-back 12175 bytes identical). (2) POST /repos//issues/2060/comments (this 'os-dev-report (rework 1)' comment). git push x2 to the same branch, no force. 0 label writes, 0 pr_create. First round: 2 (pr_create, first report comment).",
    "open_questions": [
    {
    "question": "Per-persona landing (rep -> Sales Home, manager/exec -> Executive Overview, service agent -> the service dashboard). On 18.0.0-next.1 no landing mechanism can express this inside this claim: the console lands on the first SERVED entry and ignores visible, and the only server-side gate (requiredPermissions) takes capabilities that no persona holds. Which route?",
    "options": [
    "A. Wait for the platform: objectui's landing (resolveLandingRoute / findFirstRoute) applies the sidebar's own gates (visible, the object-read prune). This PR's gates then move the service personas off Executive Overview automatically, to Leads, their first visible entry. Per-persona homes become a small HotCRM PR that orders the entries, gating with visible on positions or current_user.can, with the service module contributing its dashboard into an app-declared top group. Cost here: 0 now, roughly 40 lines later. Blocked on the objectui finding below.",
    "B. Server-enforced now: declare capabilities (defineCapability) per landing audience and grant them in systemPermissions on sales_rep / sales_manager / system_admin / the service sets. Gate nav_home / nav_sales_home with requiredPermissions, and have the service module contribute its home into a new top group. Cost: a grant change on 4-6 permission sets, new capabilities, a new group and tests, roughly 150-250 lines, and capabilities whose only meaning is the order of the sidebar.",
    "C. Platform feature: a per-item landing marker (the spec's own homePageId tombstone names navigation[].landing as the shape if the capability returns), designed enforce-first. This is new platform surface and the largest cost.",
    "D. One landing for everyone: move Sales Home first. Reps match the activities docs, but managers and admins lose Executive Overview, quick-tour needs a rewrite in 3 locales, and agents still land on refused tiles. Not recommended."
    ],
    "recommendation": "A. Business need: measured, the agent's landing is 4x403 and the docs promise reps Sales Home. A fixes the agent with no further HotCRM change and makes the rep landing declarable. Long-term: a console that lands a member on an entry its own sidebar hides is a platform defect, and fixing it at the producer is contract-first. B builds a lasting workaround (capabilities standing in for a landing concept) and C adds surface. AI-mistake: under A, visible means one thing everywhere. B teaches 'use capabilities to order the sidebar', and C adds a key that must ship enforce-first. Startup focus: A adds no surface. B adds capabilities with no pull beyond nav, and C adds a key. If the maintainer wants the agent off Executive Overview before objectui ships, B is the only route that works on 18.0.0-next.1."
    },
    {
    "question": "App Launcher (app_launcher_page, type 'app') and Utility Bar (utility_bar_page, type 'utility'): give a path, retire, or leave? Measured as na.rep at the typed URL. App Launcher renders inline: a header, a search input and an app:launcher 'Your apps' strip with 1 app (HotCRM), which duplicates the console's own app switcher and /home 'Your apps'. Utility Bar renders inline in the main column (position static, not floating): a heading, an empty Quick Notes card (the KNOWN_EMPTY exemption in test/metadata-references.test.ts), a search input and a notifications block that draws nothing. The console has no shell slot for either. No 'Unknown component'. No doc names either page as a destination.",
    "options": [
    "Give a path: two sidebar rows, for a page that duplicates the console's switcher and a 'floating' bar that renders as an ordinary page.",
    "Retire: delete both pages, their entries in the four locale packs (pages.app_launcher_page / pages.utility_bar_page) and the KNOWN_EMPTY exemption. One small follow-up card.",
    "Leave: keep both, reachable only by typed URL."
    ],
    "recommendation": "Retire. Business need: zero pull, measured. There is no nav entry, no doc destination and no consumer, App Launcher duplicates the console, and Utility Bar's floating tools never render. Long-term: a page whose type promises a shell role the console does not give is a false affordance. If the platform ships a utility-bar slot, re-author against it then. AI-mistake: HotCRM is the exemplar app, so an AI reading it learns that type 'utility' makes a floating bar and type 'app' makes a launcher, and both are wrong. Startup focus: shipped zero-consumer surface gets no sunk-cost exemption, so retire it now with no staged window. Giving a path adds two useless rows; leaving it keeps a wrong exemplar."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: public door. As service.agent on PR #2117's branch, /_console/ redirects to /apps/crm_enterprise/dashboard/executive_dashboard, whose nav entry's visible evaluates false (the sidebar omits it): 8 no-access texts, 4x403 on POST /api/v1/analytics/dataset/query. Named producer: HotCRM crm.app.ts nav_home. · evidence: objectui b9814b334 (= @objectstack/console 18.0.0-next.1) packages/app-shell/src/console/AppContent.tsx, resolveLandingRoute -> findFirstRoute(activeApp.navigation), walks the served tree without evaluateVisibility or withoutUnreadableObjectEntries, the two gates UnifiedSidebar applies before drawing. Upstream: objectui. The landing half of #2060 item 5 is Blocked-by this. · dedupe: resolveLandingRoute, findFirstRoute visible, landing hidden nav entry, withoutUnreadableObjectEntries landing",
    "class: c · Seam: spec:PageTypeSchema 'home' ("platform-level landing page ... Use home for app-level landing") / 'utility' ("Floating utility panel") -> renderer: objectui PageRenderer HomePageLayout / UtilityPageLayout (an inline page, with only the max-width differing); spec:PageSchema.isDefault on a non-record page -> consumer: none. · reach: named producer HotCRM src/sales/pages/home.page.ts (type 'home' + isDefault: true, served with isDefault true by /api/v1/meta/page/sales_home_page) and utility_bar.page.ts (type 'utility'). pnpm validate exits 0 with no word on either; the landing is the first nav item, and utility_bar_page renders in the main column, position static. The spec contradicts itself: app.zod.ts HOME_PAGE_ID_RETIRED says 'an app's landing page IS its first navigation item'. Upstream: objectstack spec (+ objectui renderer). · dedupe: PageTypeSchema home landing, utility floating panel, isDefault non-record page, UtilityPageLayout",
    "resolved in this PR (rework 1), no longer out of scope: Sales Home's 'Quick Create' card (a 28-link copy of the sidebar under a create title) is removed, with its four pack keys.",
    "carrier: none (承接者:无) · noted, not filed: objectui UnifiedSidebar checkPerm accepts an object:verb form for nav requiredPermissions, but the server (holdsRequiredPermissions, capabilities only) strips such an entry first, so for nav that branch never runs. Recorded in PR Acceptance notes.",
    "class: c · reach: named producer HotCRM src/sales/pages/home.page.ts, which declares template 'three-column' with regions 'left_sidebar' / 'right_sidebar'. objectui ThreeColumnTemplate (packages/components/src/renderers/layout/page.tsx) slots only header / sidebar / main / aside / footer, so both render as extra blocks stacked under main at full width and their width ('small' / 'medium') is dropped. Measured as na.rep: 0 aside nodes; right_sidebar at x=280 w=1136 below main. The spec types page.template and regions[].name as free strings, and pnpm validate exits 0 silently. · Seam: spec:PageSchema.template + regions[].name -> renderer: objectui ThreeColumnTemplate (slot names undeclared in spec). · The page reads fine as one column; reported, not changed (right_sidebar is outside the rework surface). · dedupe: three-column region names, ThreeColumnTemplate sidebar aside, left_sidebar right_sidebar, region name slot"
    ]
    }

  13. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #2117 comment 6184874591 (#2060 item 5) · 2026-10-15T05:07Z

  14. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: session session_01P8mZuzuyZAHSrgQKmv6ASv · reason: item 5 landed partially (PR #2117: the sidebar gates and Sales Home's entry; the per-persona landing is blocked) · destination: pm:queue for items 7–18 (the card's p3 section); item 1's delete half waits on #2078; item 5's landing half waits on objectstack-ai/objectui#12426 and on the maintainer's choice of route
    落地 · 2026-10-15T05:08Z

    读数 探针 结果
    队列分支 git ls-remote origin 'refs/heads/gh-readonly-queue/*' 空(已出队;merged 2026-10-15T05:08Z)
    origin/main git merge-base --is-ancestor d211930b origin/main 是;origin/main = d211930b fix(nav): hide the opportunity-built entries from members who cannot read opportunities, and give Sales Home a sidebar entry (#2117),父提交 bd5fda21

    状态变更:Part of #2060,卡保持打开。第 5 项落地的部分:不读商机的角色(客服、客服经理)侧栏不再出现建在商机上的 5 个入口;Sales Home 有了侧栏入口(固定在 Home 之下);Sales Home 上画侧栏副本的「Quick Create」卡片删除;三语文档同步。

    第 5 项未完成的部分:

  15. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Oct 15, 2026
  16. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Rulings on item 5's two open questions, given by the maintainer in the repo:hotcrm seat's chat (session_01P8mZuzuyZAHSrgQKmv6ASv), 2026-10-15T06:17Z. ⛔ Not a claim.

    1. Per-persona landing → A, verbatim 「A 等平台修复(推荐)」: wait for console: an app opens on its first SERVED navigation entry even when the sidebar hides that entry from the member (visible false, or an object the member cannot read) objectui#12426 (the console's landing applies the sidebar's own gates). Once a published @objectstack/* pin carries that fix, the service personas move off Executive Overview by PR fix(nav): hide the opportunity-built entries from members who cannot read opportunities, and give Sales Home a sidebar entry #2117's gates alone; a small HotCRM PR then orders and gates the entries so each persona's first visible entry is its home (rep → Sales Home, manager / exec → Executive Overview, agent → the Customer Service dashboard contributed by the service module). Until then this half of item 5 stays here, Blocked-by: objectstack-ai/objectui#12426. Options B (capabilities + a grant change), C (a platform landing marker) and D (Sales Home first for everyone) are not taken.
    2. App Launcher / Utility Bar → retire, verbatim 「下线(推荐)」: filed as its own card (the seat links it below this comment) and dispatched serially on this ruling.

    Generated by Claude Code

  17. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling 2 (retire App Launcher / Utility Bar) → #2118, dispatched serially on it · 2026-10-15T06:18Z

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 workingpm:queueReady for the PM dispatch looppriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions