feat: cross-linking between all entity types #31

Closed
opened 2026-05-05 08:06:59 +00:00 by casper-stevens · 4 comments
Member

Context

Ref: lhumina_code/home#210

Currently each entity type is a disconnected database. The app must feel like one connected system. Every detail page should surface its related records as clickable links.

Company detail page

  • Linked contacts (people at this company)
  • Linked deals
  • Linked contracts
  • Linked finance records
  • Linked HR records
  • Linked tasks/projects
  • Linked interactions

Contact detail page (/persons)

  • Linked companies + role/title at each
  • Contact details (email, phone, etc.)
  • Linked interactions
  • Linked deals
  • Linked contracts
  • Linked HR record (if applicable)
  • Linked tasks/projects
  • Linked finance records (if relevant)

Interaction detail page (/contacts)

  • Linked contact (name, clickable)
  • Linked company (if relevant)
  • Subject, type, direction, date, follow-up date, notes
  • Linked task (if relevant)

Deal detail page

  • Linked company
  • Linked contacts
  • Amount with currency formatting
  • Related contracts
  • Related tasks
  • Related interactions/follow-ups

Contract detail page

  • Linked company
  • Linked contacts
  • Contract value (formatted)
  • Status and dates
  • Related tasks
  • Related finance records
  • Related interactions

Finance detail page

  • Linked company / contact / deal / contract
  • Formatted amount with currency
  • Status and dates

Task/Project detail page

  • Linked company (if relevant)
  • Linked contact (if relevant)
  • Linked deal, contract, HR, or finance item (if relevant)
  • Status, owner, due date

Implementation notes

  • Each section of related records should be a small table or card list with name + link
  • If a name/title is too long for the grid, truncate with ellipsis
  • Related records should only show if the link field is populated — no empty sections
  • Use the existing SID-to-name resolution pattern already implemented for Instruments and Contracts

Acceptance criteria

  • Company detail page lists its contacts, deals, contracts, finance records, and interactions
  • Contact detail page lists its companies, interactions, deals, and contracts
  • All related record entries are clickable links to the detail page of that record
  • No raw SIDs shown as the primary identifier in any cross-link section
## Context Ref: [lhumina_code/home#210](https://forge.ourworld.tf/lhumina_code/home/issues/210) Currently each entity type is a disconnected database. The app must feel like one connected system. Every detail page should surface its related records as clickable links. ## Required cross-links per entity ### Company detail page - Linked contacts (people at this company) - Linked deals - Linked contracts - Linked finance records - Linked HR records - Linked tasks/projects - Linked interactions ### Contact detail page (`/persons`) - Linked companies + role/title at each - Contact details (email, phone, etc.) - Linked interactions - Linked deals - Linked contracts - Linked HR record (if applicable) - Linked tasks/projects - Linked finance records (if relevant) ### Interaction detail page (`/contacts`) - Linked contact (name, clickable) - Linked company (if relevant) - Subject, type, direction, date, follow-up date, notes - Linked task (if relevant) ### Deal detail page - Linked company - Linked contacts - Amount with currency formatting - Related contracts - Related tasks - Related interactions/follow-ups ### Contract detail page - Linked company - Linked contacts - Contract value (formatted) - Status and dates - Related tasks - Related finance records - Related interactions ### Finance detail page - Linked company / contact / deal / contract - Formatted amount with currency - Status and dates ### Task/Project detail page - Linked company (if relevant) - Linked contact (if relevant) - Linked deal, contract, HR, or finance item (if relevant) - Status, owner, due date ## Implementation notes - Each section of related records should be a small table or card list with name + link - If a name/title is too long for the grid, truncate with ellipsis - Related records should only show if the link field is populated — no empty sections - Use the existing SID-to-name resolution pattern already implemented for Instruments and Contracts ## Acceptance criteria - Company detail page lists its contacts, deals, contracts, finance records, and interactions - Contact detail page lists its companies, interactions, deals, and contracts - All related record entries are clickable links to the detail page of that record - No raw SIDs shown as the primary identifier in any cross-link section
Author
Member

Implementation Spec for Issue #31

Objective

Surface cross-linked related records as clickable links on every entity detail page, making the app feel like one connected system. Each related-records section shows name + link (not raw SIDs), truncates long names with CSS ellipsis, and hides when the list is empty.


Current State Analysis

Root cause of all missing cross-links: Every get_*_for_* method in services/mod.rs (lines ~1292–1535) returns Ok(Vec::new()) — the relationship query stubs were never implemented.

Data model readiness:

  • Person.company_sid — can list contacts per company
  • ContactRecord.person_sid, company_sid, contract_sid, deal_sid — can filter interactions per entity
  • Deal.company_sid, person_sid, contract_sid — can reverse-lookup deals
  • Contract.company_sid, person_sid — can filter contracts per entity
  • Transaction.contract_sid — can resolve up to company/person/deal via 2-hop
  • Project.company_sid, owner_sid — can list projects per company/person
  • Task.project_sid, assignee_sid — company link resolved via project (2-hop)
  • Instrument.company_sid — can list instruments per company

Bugs found: The "Interactions" sections on Company, Person, and Contract detail pages already display date/type/subject rows but have no anchor tags — the records are not linked to /interactions/{id}.


Files to Modify

  • crates/hero_biz_ui/src/services/mod.rs — implement all stub get_*_for_* methods; add new get_persons_for_company, get_projects_for_company, get_transactions_for_company, get_transactions_for_person, get_deals_for_contract
  • crates/hero_biz_ui/src/web/handlers/mod.rs — update companies_detail, persons_detail, deals_detail, contracts_detail, transactions_detail, tasks_detail, hr_detail to fetch newly available related records
  • crates/hero_biz_ui/src/web/templates/mod.rs — update CompanyDetailTemplate, PersonDetailTemplate, DealDetailTemplate, ContractDetailTemplate, TransactionDetailTemplate, TaskDetailTemplate, HrDetailTemplate to render new cross-link sections

Implementation Plan

Step 1: Implement all stub store relationship query methods

Files: crates/hero_biz_ui/src/services/mod.rs

Pattern: load_all_* then .into_iter().filter(|x| x.field_sid.as_deref() == Some(target_sid)).collect()

Implement:

  • get_contracts_for_person, get_contracts_for_company, get_contracts_for_instrument
  • get_contacts_for_person, get_contacts_for_company, get_contacts_for_contract, get_contacts_for_deal
  • get_deals_for_person, get_deals_for_company
  • get_instruments_for_company
  • get_transactions_for_contract
  • get_projects_for_person
  • get_milestones_for_project, get_milestones_for_person
  • get_tasks_for_project, get_tasks_for_milestone, get_tasks_for_person
  • get_opportunities_for_person, get_opportunities_for_company

Add new methods:

  • get_persons_for_company (Person has company_sid)
  • get_projects_for_company (Project has company_sid)
  • get_transactions_for_company (2-hop: contracts for company → transactions for each contract)
  • get_transactions_for_person (2-hop: contracts for person → transactions for each contract)
  • get_deals_for_contract (Deal has contract_sid — reverse lookup)

Dependencies: none


Files: crates/hero_biz_ui/src/web/templates/mod.rs, crates/hero_biz_ui/src/web/handlers/mod.rs

  • Add pub persons: Vec<Person>, pub transactions: Vec<Transaction>, pub projects: Vec<Project> to CompanyDetailTemplate
  • Add persons_section, transactions_section, projects_section to render()
  • Fix contacts_rows to wrap each interaction date in <a href="{bp}/c/{context}/interactions/{id}">...</a>
  • Handler: add calls to get_persons_for_company, get_transactions_for_company, get_projects_for_company

Dependencies: Step 1


Files: crates/hero_biz_ui/src/web/templates/mod.rs, crates/hero_biz_ui/src/web/handlers/mod.rs

  • Add pub transactions: Vec<Transaction> to PersonDetailTemplate
  • Add transactions_section to render()
  • Fix contacts_rows to link each interaction to /interactions/{id}
  • Handler: add call to get_transactions_for_person

Dependencies: Step 1


Step 4: Deal detail — add Interactions section

Files: crates/hero_biz_ui/src/web/templates/mod.rs, crates/hero_biz_ui/src/web/handlers/mod.rs

  • Add pub contacts: Vec<Contact> to DealDetailTemplate
  • Add contacts_section (interactions/follow-ups) to render() with links to /interactions/{id}
  • Handler: add call to get_contacts_for_deal

Dependencies: Step 1


Files: crates/hero_biz_ui/src/web/templates/mod.rs, crates/hero_biz_ui/src/web/handlers/mod.rs

  • Add pub deals: Vec<Deal> to ContractDetailTemplate
  • Add deals_section to render() with links to /deals/{id}
  • Fix existing contacts_rows to link each interaction to /interactions/{id}
  • Handler: add call to get_deals_for_contract

Dependencies: Step 1


Files: crates/hero_biz_ui/src/web/templates/mod.rs, crates/hero_biz_ui/src/web/handlers/mod.rs

  • Add pub company: Option<Company>, pub person: Option<Person>, pub deal: Option<Deal> to TransactionDetailTemplate
  • Add company_link, person_link, deal_link rows to render()
  • Handler: after loading contract, resolve company (via contract.company_sid), person (via contract.person_sid), deal (via get_deals_for_contract)

Dependencies: Step 1


Files: crates/hero_biz_ui/src/web/templates/mod.rs, crates/hero_biz_ui/src/web/handlers/mod.rs

  • Add pub company: Option<Company> to TaskDetailTemplate
  • Resolve company: handler already loads project — if project.company_sid is set, load company
  • Add company_link row to render()

Dependencies: Step 1


Step 8: HR detail — add Contracts, Deals, Projects, Tasks sections

Files: crates/hero_biz_ui/src/web/templates/mod.rs, crates/hero_biz_ui/src/web/handlers/mod.rs

  • Add pub contracts: Vec<Contract>, pub deals: Vec<Deal>, pub projects: Vec<Project>, pub tasks: Vec<Task> to HrDetailTemplate
  • Add corresponding sections in render()
  • Handler: add calls to get_contracts_for_person, get_deals_for_person, get_projects_for_person, get_tasks_for_person

Dependencies: Step 1


URL Slug Reference

Entity URL slug
Person/Contact /contacts/{id}
Company /companies/{id}
Interaction (ContactRecord) /interactions/{id}
Deal /deals/{id}
Contract /contracts/{id}
Transaction (Finance) /transactions/{id}
Project /projects/{id}
Task /tasks/{id}
HR /hr/{id}

Acceptance Criteria

  • Company detail lists contacts (persons), deals, contracts, transactions, projects, and interactions — all as clickable links
  • Contact/Person detail lists interactions, deals, contracts, and transactions — all as clickable links
  • Deal detail shows clickable interactions/follow-ups section
  • Contract detail shows clickable deals section; interaction entries are linked
  • Finance/Transaction detail shows company, person, deal as clickable links alongside the contract link
  • Task detail shows company (resolved via project) as a clickable link
  • HR detail shows contracts, deals, projects, and tasks as clickable link sections
  • No raw SIDs displayed as primary identifiers in any cross-link section
  • All related sections hidden when no linked records exist (handled by render_linked_section)
  • All existing detail page functionality unchanged
## Implementation Spec for Issue #31 ### Objective Surface cross-linked related records as clickable links on every entity detail page, making the app feel like one connected system. Each related-records section shows name + link (not raw SIDs), truncates long names with CSS ellipsis, and hides when the list is empty. --- ### Current State Analysis **Root cause of all missing cross-links:** Every `get_*_for_*` method in `services/mod.rs` (lines ~1292–1535) returns `Ok(Vec::new())` — the relationship query stubs were never implemented. **Data model readiness:** - `Person.company_sid` — can list contacts per company - `ContactRecord.person_sid`, `company_sid`, `contract_sid`, `deal_sid` — can filter interactions per entity - `Deal.company_sid`, `person_sid`, `contract_sid` — can reverse-lookup deals - `Contract.company_sid`, `person_sid` — can filter contracts per entity - `Transaction.contract_sid` — can resolve up to company/person/deal via 2-hop - `Project.company_sid`, `owner_sid` — can list projects per company/person - `Task.project_sid`, `assignee_sid` — company link resolved via project (2-hop) - `Instrument.company_sid` — can list instruments per company **Bugs found:** The "Interactions" sections on Company, Person, and Contract detail pages already display date/type/subject rows but have no anchor tags — the records are not linked to `/interactions/{id}`. --- ### Files to Modify - `crates/hero_biz_ui/src/services/mod.rs` — implement all stub `get_*_for_*` methods; add new `get_persons_for_company`, `get_projects_for_company`, `get_transactions_for_company`, `get_transactions_for_person`, `get_deals_for_contract` - `crates/hero_biz_ui/src/web/handlers/mod.rs` — update `companies_detail`, `persons_detail`, `deals_detail`, `contracts_detail`, `transactions_detail`, `tasks_detail`, `hr_detail` to fetch newly available related records - `crates/hero_biz_ui/src/web/templates/mod.rs` — update `CompanyDetailTemplate`, `PersonDetailTemplate`, `DealDetailTemplate`, `ContractDetailTemplate`, `TransactionDetailTemplate`, `TaskDetailTemplate`, `HrDetailTemplate` to render new cross-link sections --- ### Implementation Plan #### Step 1: Implement all stub store relationship query methods Files: `crates/hero_biz_ui/src/services/mod.rs` Pattern: `load_all_*` then `.into_iter().filter(|x| x.field_sid.as_deref() == Some(target_sid)).collect()` Implement: - `get_contracts_for_person`, `get_contracts_for_company`, `get_contracts_for_instrument` - `get_contacts_for_person`, `get_contacts_for_company`, `get_contacts_for_contract`, `get_contacts_for_deal` - `get_deals_for_person`, `get_deals_for_company` - `get_instruments_for_company` - `get_transactions_for_contract` - `get_projects_for_person` - `get_milestones_for_project`, `get_milestones_for_person` - `get_tasks_for_project`, `get_tasks_for_milestone`, `get_tasks_for_person` - `get_opportunities_for_person`, `get_opportunities_for_company` Add new methods: - `get_persons_for_company` (Person has `company_sid`) - `get_projects_for_company` (Project has `company_sid`) - `get_transactions_for_company` (2-hop: contracts for company → transactions for each contract) - `get_transactions_for_person` (2-hop: contracts for person → transactions for each contract) - `get_deals_for_contract` (Deal has `contract_sid` — reverse lookup) Dependencies: none --- #### Step 2: Company detail — add Persons and Transactions sections; fix Interactions links Files: `crates/hero_biz_ui/src/web/templates/mod.rs`, `crates/hero_biz_ui/src/web/handlers/mod.rs` - Add `pub persons: Vec<Person>`, `pub transactions: Vec<Transaction>`, `pub projects: Vec<Project>` to `CompanyDetailTemplate` - Add `persons_section`, `transactions_section`, `projects_section` to `render()` - Fix `contacts_rows` to wrap each interaction date in `<a href="{bp}/c/{context}/interactions/{id}">...</a>` - Handler: add calls to `get_persons_for_company`, `get_transactions_for_company`, `get_projects_for_company` Dependencies: Step 1 --- #### Step 3: Contact/Person detail — add Transactions section; fix Interactions links Files: `crates/hero_biz_ui/src/web/templates/mod.rs`, `crates/hero_biz_ui/src/web/handlers/mod.rs` - Add `pub transactions: Vec<Transaction>` to `PersonDetailTemplate` - Add `transactions_section` to `render()` - Fix `contacts_rows` to link each interaction to `/interactions/{id}` - Handler: add call to `get_transactions_for_person` Dependencies: Step 1 --- #### Step 4: Deal detail — add Interactions section Files: `crates/hero_biz_ui/src/web/templates/mod.rs`, `crates/hero_biz_ui/src/web/handlers/mod.rs` - Add `pub contacts: Vec<Contact>` to `DealDetailTemplate` - Add `contacts_section` (interactions/follow-ups) to `render()` with links to `/interactions/{id}` - Handler: add call to `get_contacts_for_deal` Dependencies: Step 1 --- #### Step 5: Contract detail — add Deals section; fix Interactions links Files: `crates/hero_biz_ui/src/web/templates/mod.rs`, `crates/hero_biz_ui/src/web/handlers/mod.rs` - Add `pub deals: Vec<Deal>` to `ContractDetailTemplate` - Add `deals_section` to `render()` with links to `/deals/{id}` - Fix existing `contacts_rows` to link each interaction to `/interactions/{id}` - Handler: add call to `get_deals_for_contract` Dependencies: Step 1 --- #### Step 6: Finance/Transaction detail — add Company, Person, Deal cross-links Files: `crates/hero_biz_ui/src/web/templates/mod.rs`, `crates/hero_biz_ui/src/web/handlers/mod.rs` - Add `pub company: Option<Company>`, `pub person: Option<Person>`, `pub deal: Option<Deal>` to `TransactionDetailTemplate` - Add `company_link`, `person_link`, `deal_link` rows to `render()` - Handler: after loading contract, resolve company (via `contract.company_sid`), person (via `contract.person_sid`), deal (via `get_deals_for_contract`) Dependencies: Step 1 --- #### Step 7: Task detail — add Company cross-link via project Files: `crates/hero_biz_ui/src/web/templates/mod.rs`, `crates/hero_biz_ui/src/web/handlers/mod.rs` - Add `pub company: Option<Company>` to `TaskDetailTemplate` - Resolve company: handler already loads `project` — if `project.company_sid` is set, load company - Add `company_link` row to `render()` Dependencies: Step 1 --- #### Step 8: HR detail — add Contracts, Deals, Projects, Tasks sections Files: `crates/hero_biz_ui/src/web/templates/mod.rs`, `crates/hero_biz_ui/src/web/handlers/mod.rs` - Add `pub contracts: Vec<Contract>`, `pub deals: Vec<Deal>`, `pub projects: Vec<Project>`, `pub tasks: Vec<Task>` to `HrDetailTemplate` - Add corresponding sections in `render()` - Handler: add calls to `get_contracts_for_person`, `get_deals_for_person`, `get_projects_for_person`, `get_tasks_for_person` Dependencies: Step 1 --- ### URL Slug Reference | Entity | URL slug | |---|---| | Person/Contact | `/contacts/{id}` | | Company | `/companies/{id}` | | Interaction (ContactRecord) | `/interactions/{id}` | | Deal | `/deals/{id}` | | Contract | `/contracts/{id}` | | Transaction (Finance) | `/transactions/{id}` | | Project | `/projects/{id}` | | Task | `/tasks/{id}` | | HR | `/hr/{id}` | --- ### Acceptance Criteria - [ ] Company detail lists contacts (persons), deals, contracts, transactions, projects, and interactions — all as clickable links - [ ] Contact/Person detail lists interactions, deals, contracts, and transactions — all as clickable links - [ ] Deal detail shows clickable interactions/follow-ups section - [ ] Contract detail shows clickable deals section; interaction entries are linked - [ ] Finance/Transaction detail shows company, person, deal as clickable links alongside the contract link - [ ] Task detail shows company (resolved via project) as a clickable link - [ ] HR detail shows contracts, deals, projects, and tasks as clickable link sections - [ ] No raw SIDs displayed as primary identifiers in any cross-link section - [ ] All related sections hidden when no linked records exist (handled by `render_linked_section`) - [ ] All existing detail page functionality unchanged
Author
Member

Test Results

  • Total: 1
  • Passed: 1
  • Failed: 0

cargo test -p hero_biz_ui — 1 test passed, 0 failed.

Build: clean (cargo build -p hero_biz_ui in 11.83s, 0 errors, 0 warnings).

## Test Results - Total: 1 - Passed: 1 - Failed: 0 `cargo test -p hero_biz_ui` — 1 test passed, 0 failed. Build: clean (`cargo build -p hero_biz_ui` in 11.83s, 0 errors, 0 warnings).
Author
Member

Implementation Summary

Closes #31.

Changes

crates/hero_biz_ui/src/services/mod.rs

  • Implemented all 20 stub get_*_for_* relationship query methods (all previously returned empty)
  • Added 6 new methods: get_persons_for_company, get_projects_for_company, get_transactions_for_company, get_transactions_for_person, get_deals_for_contract, get_contacts_for_deal

crates/hero_biz_ui/src/web/handlers/mod.rs

  • companies_detail: fetches persons, transactions, projects for company
  • persons_detail: fetches transactions for person
  • deals_detail: fetches interactions (ContactRecords) for deal
  • contracts_detail: fetches deals for contract
  • transactions_detail: resolves company and person via contract, resolves deal via reverse lookup
  • tasks_detail: resolves company via project (2-hop)
  • hr_detail: fetches contracts, deals, projects, tasks for person

crates/hero_biz_ui/src/web/templates/mod.rs

  • CompanyDetailTemplate: added Contacts, Finance, Projects sections; fixed Interactions rows to link to /interactions/{id}
  • PersonDetailTemplate: added Finance section; fixed Interactions rows to link to /interactions/{id}
  • DealDetailTemplate: added Interactions section
  • ContractDetailTemplate: added Deals section; fixed Interactions rows to link to /interactions/{id}
  • TransactionDetailTemplate: added Company, Person, Deal cross-links
  • TaskDetailTemplate: added Company cross-link (resolved via project)
  • HrDetailTemplate: wired existing Contracts, Deals, Projects, Tasks sections into HTML output (rows were built but not rendered)

Notes

  • Instrument.company_sid and Transaction.contract_sid are plain String (not Option<String>); filter uses .as_str() comparison
  • Task→Company is a 2-hop via task.project_sid → project.company_sid; no model change needed
  • All related sections are hidden when empty via the existing render_linked_section helper
## Implementation Summary Closes #31. ### Changes **`crates/hero_biz_ui/src/services/mod.rs`** - Implemented all 20 stub `get_*_for_*` relationship query methods (all previously returned empty) - Added 6 new methods: `get_persons_for_company`, `get_projects_for_company`, `get_transactions_for_company`, `get_transactions_for_person`, `get_deals_for_contract`, `get_contacts_for_deal` **`crates/hero_biz_ui/src/web/handlers/mod.rs`** - `companies_detail`: fetches persons, transactions, projects for company - `persons_detail`: fetches transactions for person - `deals_detail`: fetches interactions (ContactRecords) for deal - `contracts_detail`: fetches deals for contract - `transactions_detail`: resolves company and person via contract, resolves deal via reverse lookup - `tasks_detail`: resolves company via project (2-hop) - `hr_detail`: fetches contracts, deals, projects, tasks for person **`crates/hero_biz_ui/src/web/templates/mod.rs`** - `CompanyDetailTemplate`: added Contacts, Finance, Projects sections; fixed Interactions rows to link to `/interactions/{id}` - `PersonDetailTemplate`: added Finance section; fixed Interactions rows to link to `/interactions/{id}` - `DealDetailTemplate`: added Interactions section - `ContractDetailTemplate`: added Deals section; fixed Interactions rows to link to `/interactions/{id}` - `TransactionDetailTemplate`: added Company, Person, Deal cross-links - `TaskDetailTemplate`: added Company cross-link (resolved via project) - `HrDetailTemplate`: wired existing Contracts, Deals, Projects, Tasks sections into HTML output (rows were built but not rendered) ### Notes - `Instrument.company_sid` and `Transaction.contract_sid` are plain `String` (not `Option<String>`); filter uses `.as_str()` comparison - Task→Company is a 2-hop via `task.project_sid → project.company_sid`; no model change needed - All related sections are hidden when empty via the existing `render_linked_section` helper
Author
Member

Implemented in commit 76347f9: surface related records as clickable cross-links on all entity detail pages (Company, Contact, Deal, Contract, Opportunity).

Implemented in commit 76347f9: surface related records as clickable cross-links on all entity detail pages (Company, Contact, Deal, Contract, Opportunity).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_biz#31
No description provided.