Bug: chat and file upload stubs non-functional; Deal/Task link selector gaps; dead code #10

Closed
opened 2026-04-30 12:36:32 +00:00 by casper-stevens · 5 comments
Member

Critical — Broken features (stubs that are actively called)

Chat is entirely non-functional

save_chat_message (services/mod.rs:2271) returns Err("Chat save not implemented"). load_chat_messages (services/mod.rs:2263) always returns Ok(Vec::new()). Both are called from multiple handlers (lines 3010, 3068, 3174, 3220, 3504, 3549, 4456). The AI assistant chat UI exists and accepts input but nothing is ever stored or retrieved.

File uploads are silently dropped

save_upload (services/mod.rs:1562) returns Ok("uploads/placeholder") and writes nothing. Called from handler line 3123. Uploads appear to succeed but the data is lost.


Deal

  • Model has instrument_sid: String and contract_sid: Option<String>
  • update_deal_links ignores both
  • get_link_selector does not offer instruments or contracts for deals
  • Fix: add both to update_deal_links and to the Deal branch in get_link_selector

Task

  • Model has story_sid: Option<String>
  • update_task_links never touches it
  • get_link_selector does not offer stories for tasks
  • Fix: add stories to update_task_links and to the Task branch in get_link_selector

Note — Fields wiped on edit (tracked separately in #11)

Because saves DO reach OSIS, the hardcoded-None fields in update handlers (contact, deal, task, project, milestone) cause real data loss on every edit. See issue #11 for the full list.


Low — Dead code

save_to_space (services/mod.rs:1551) is a no-op stub but is never called — safe to delete.

get_link_selector_data service method and LinkSelectorData struct are unreferenced after the get_link_selector handler rewrite — safe to delete.


Low — Stale docs

instructions/coding_instructions.md and instructions/coding_instructions_ai.md describe the old heroscript/TOML pipeline — completely obsolete. Should be archived or removed.


Suggested order

  1. Implement save_chat_message / load_chat_messages via a storage backend
  2. Implement save_upload
  3. Fix Deal and Task link selector gaps
  4. Remove dead code (save_to_space, get_link_selector_data, LinkSelectorData)
  5. Archive stale instruction docs
## Critical — Broken features (stubs that are actively called) ### Chat is entirely non-functional `save_chat_message` (`services/mod.rs:2271`) returns `Err("Chat save not implemented")`. `load_chat_messages` (`services/mod.rs:2263`) always returns `Ok(Vec::new())`. Both are called from multiple handlers (lines 3010, 3068, 3174, 3220, 3504, 3549, 4456). The AI assistant chat UI exists and accepts input but nothing is ever stored or retrieved. ### File uploads are silently dropped `save_upload` (`services/mod.rs:1562`) returns `Ok("uploads/placeholder")` and writes nothing. Called from handler line 3123. Uploads appear to succeed but the data is lost. --- ## High — Link selector / model gaps ### Deal - Model has `instrument_sid: String` and `contract_sid: Option<String>` - `update_deal_links` ignores both - `get_link_selector` does not offer instruments or contracts for deals - Fix: add both to `update_deal_links` and to the Deal branch in `get_link_selector` ### Task - Model has `story_sid: Option<String>` - `update_task_links` never touches it - `get_link_selector` does not offer stories for tasks - Fix: add stories to `update_task_links` and to the Task branch in `get_link_selector` --- ## Note — Fields wiped on edit (tracked separately in #11) Because saves DO reach OSIS, the hardcoded-`None` fields in update handlers (contact, deal, task, project, milestone) cause real data loss on every edit. See issue #11 for the full list. --- ## Low — Dead code `save_to_space` (`services/mod.rs:1551`) is a no-op stub but is **never called** — safe to delete. `get_link_selector_data` service method and `LinkSelectorData` struct are unreferenced after the `get_link_selector` handler rewrite — safe to delete. --- ## Low — Stale docs `instructions/coding_instructions.md` and `instructions/coding_instructions_ai.md` describe the old heroscript/TOML pipeline — completely obsolete. Should be archived or removed. --- ## Suggested order 1. Implement `save_chat_message` / `load_chat_messages` via a storage backend 2. Implement `save_upload` 3. Fix Deal and Task link selector gaps 4. Remove dead code (`save_to_space`, `get_link_selector_data`, `LinkSelectorData`) 5. Archive stale instruction docs
Author
Member

Implementation Spec

Objective

Replace the no-op save_to_space stub with real SDK calls in every update_*_links function; wire the missing instrument_sid, contract_sid (Deal) and story_sid (Task) fields through their link selectors and save paths; resolve linked entities on five detail pages that currently show nothing; and remove dead code and stale documentation.


Files to Modify

  • crates/hero_biz_ui/src/web/handlers/mod.rsupdate_*_links, get_link_selector, detail handlers
  • crates/hero_biz_ui/src/web/templates/mod.rs — ContactDetail, DealDetail, TaskDetail, MilestoneDetail, ProjectDetail templates
  • crates/hero_biz_ui/src/services/mod.rs — add load_all_stories_for_space/load_story_for_space; remove get_link_selector_data + LinkSelectorData
  • instructions/coding_instructions.md — replace stale heroscript/TOML content
  • instructions/coding_instructions_ai.md — replace stale content

Implementation Plan

Files: handlers/mod.rs
Each function ends with store.save_to_space(...) which is a no-op. Replace with the matching store.save_*(context, &entity).await call. Mapping:

Function New call
update_deal_links `store.save_deal(context, &deal).await.map(
update_contact_links `store.save_contact(context, &contact).await.map(
update_contract_links `store.save_contract(context, &contract).await.map(
update_opportunity_links `store.save_opportunity(context, &opportunity).await.map(
update_project_links `store.save_project(context, &project).await.map(
update_task_links `store.save_task(context, &task).await.map(
update_milestone_links `store.save_milestone(context, &milestone).await.map(
update_instrument_links `store.save_instrument(context, &instrument).await.map(
update_transaction_links `store.save_transaction(context, &transaction).await.map(

Error conversion: .map_err(|e| anyhow::anyhow!(e.to_string())) on each.
Dependencies: none.

Files: handlers/mod.rs
Add to update_deal_links: deal.instrument_sid from key "instruments", deal.contract_sid from key "contracts". Remove the incorrect comment "Deal doesn't have instrument_sid or contract_sid fields".
Dependencies: none (parallel with Step 1).

Files: handlers/mod.rs
Add to update_task_links: task.story_sid from key "stories".
Dependencies: none (parallel with Steps 1–2).

Step 4 — Add story service methods

Files: services/mod.rs
Add load_all_stories_for_space(context) -> Result<Vec<Story>> and load_story_for_space(sid, context) -> Result<Story> following the same pattern as milestones: call projects_for(context).story_list() for SIDs, then story_get(sid) per item, map SDK type to local Story model.
Dependencies: none (parallel with Steps 1–3).

Files: handlers/mod.rs
Deal branch: add opt_link for instrument_sid and contract_sid; load instruments and contracts from store; add "instruments" and "contracts" to linkable_types.
Task branch: add opt_link for story_sid; load stories from store (Step 4 must be done first); add "stories" to linkable_types.
Dependencies: Step 4.

Step 6 — Fix contacts_detail handler

Files: handlers/mod.rs
Load contract, opportunity, deal from their SIDs on the contact. Pass to ContactDetailTemplate.
Dependencies: none (parallel with Step 5).

Step 7 — Fix deals_detail handler

Files: handlers/mod.rs
Load opportunity, instrument, contract from their SIDs on the deal. Pass to DealDetailTemplate.
Dependencies: none.

Step 8 — Fix tasks_detail handler

Files: handlers/mod.rs
Load story from task.story_sid. Pass to TaskDetailTemplate.
Dependencies: Step 4.

Step 9 — Fix milestones_detail handler

Files: handlers/mod.rs
Load owner (a Person) from milestone.owner_sid. Pass to MilestoneDetailTemplate.
Dependencies: none.

Step 10 — Fix projects_detail handler

Files: handlers/mod.rs
Replace let owner: Option<Person> = None; with a real lookup using project.owner_sid. Pass to ProjectDetailTemplate.
Dependencies: none.

Step 11 — Update ContactDetailTemplate

Files: templates/mod.rs
Add contract: Option<Contract>, opportunity: Option<Opportunity>, deal: Option<Deal> to struct. Add three hyperlinked rows to the HTML render.
Dependencies: Step 6.

Step 12 — Update DealDetailTemplate

Files: templates/mod.rs
Add opportunity: Option<Opportunity>, instrument: Option<Instrument>, contract: Option<Contract>. Add three hyperlinked rows.
Dependencies: Step 7.

Step 13 — Update TaskDetailTemplate

Files: templates/mod.rs
Add story: Option<Story>. Add one hyperlinked row.
Dependencies: Step 8.

Step 14 — Update MilestoneDetailTemplate

Files: templates/mod.rs
Add owner: Option<Person>. Add one hyperlinked row.
Dependencies: Step 9.

Step 15 — Remove dead code

Files: services/mod.rs
Delete get_link_selector_data method and LinkSelectorData struct. Verify no callers remain.
Dependencies: none.

Step 16 — Replace stale instruction docs

Files: instructions/coding_instructions.md, instructions/coding_instructions_ai.md
Replace heroscript/TOML-based content with accurate description of the current architecture: Axum web server backed by hero_osis_sdk (BusinessClient, ProjectsClient, IdentityClient per context).
Dependencies: none.


Acceptance Criteria

  • cargo build passes with no errors
  • Link selector saves actually persist to hero_osis (deal instruments/contracts, task stories)
  • Deal link selector shows instruments and contracts
  • Task link selector shows stories
  • Contact detail displays contract, opportunity, deal as links when set
  • Deal detail displays opportunity, instrument, contract as links
  • Task detail displays story as a link
  • Milestone detail displays owner as a link
  • Project detail displays owner as a link (not blank)
  • LinkSelectorData and get_link_selector_data are gone
  • instructions/ docs no longer reference heroscript or TOML database

Notes

  • save_to_space itself is NOT deleted — it is still called from other paths (e.g. file uploads). Leave it with its TODO comment.
  • The ProjectDetailTemplate struct already has an owner: Option<Person> field — only the handler and the comment need updating.
  • For error conversion in update_*_links: check whether HeroBizError implements std::error::Error; use .map_err(anyhow::Error::from) if it does, otherwise .map_err(|e| anyhow::anyhow!(e.to_string())).
  • Steps 1, 2, 3, 4, 6, 7, 9, 10, 15, 16 are fully independent and can proceed in parallel. Steps 5 and 8 depend on Step 4. Steps 11–14 depend on Steps 6–9 respectively.
## Implementation Spec ### Objective Replace the no-op `save_to_space` stub with real SDK calls in every `update_*_links` function; wire the missing `instrument_sid`, `contract_sid` (Deal) and `story_sid` (Task) fields through their link selectors and save paths; resolve linked entities on five detail pages that currently show nothing; and remove dead code and stale documentation. --- ### Files to Modify - `crates/hero_biz_ui/src/web/handlers/mod.rs` — `update_*_links`, `get_link_selector`, detail handlers - `crates/hero_biz_ui/src/web/templates/mod.rs` — ContactDetail, DealDetail, TaskDetail, MilestoneDetail, ProjectDetail templates - `crates/hero_biz_ui/src/services/mod.rs` — add `load_all_stories_for_space`/`load_story_for_space`; remove `get_link_selector_data` + `LinkSelectorData` - `instructions/coding_instructions.md` — replace stale heroscript/TOML content - `instructions/coding_instructions_ai.md` — replace stale content --- ### Implementation Plan #### Step 1 — Replace `save_to_space` in all nine `update_*_links` functions Files: `handlers/mod.rs` Each function ends with `store.save_to_space(...)` which is a no-op. Replace with the matching `store.save_*(context, &entity).await` call. Mapping: | Function | New call | |---|---| | `update_deal_links` | `store.save_deal(context, &deal).await.map(|_|())` | | `update_contact_links` | `store.save_contact(context, &contact).await.map(|_|())` | | `update_contract_links` | `store.save_contract(context, &contract).await.map(|_|())` | | `update_opportunity_links` | `store.save_opportunity(context, &opportunity).await.map(|_|())` | | `update_project_links` | `store.save_project(context, &project).await.map(|_|())` | | `update_task_links` | `store.save_task(context, &task).await.map(|_|())` | | `update_milestone_links` | `store.save_milestone(context, &milestone).await.map(|_|())` | | `update_instrument_links` | `store.save_instrument(context, &instrument).await.map(|_|())` | | `update_transaction_links` | `store.save_transaction(context, &transaction).await.map(|_|())` | Error conversion: `.map_err(|e| anyhow::anyhow!(e.to_string()))` on each. Dependencies: none. #### Step 2 — Fix Deal link field gaps Files: `handlers/mod.rs` Add to `update_deal_links`: `deal.instrument_sid` from key `"instruments"`, `deal.contract_sid` from key `"contracts"`. Remove the incorrect comment "Deal doesn't have instrument_sid or contract_sid fields". Dependencies: none (parallel with Step 1). #### Step 3 — Fix Task link field gap Files: `handlers/mod.rs` Add to `update_task_links`: `task.story_sid` from key `"stories"`. Dependencies: none (parallel with Steps 1–2). #### Step 4 — Add story service methods Files: `services/mod.rs` Add `load_all_stories_for_space(context) -> Result<Vec<Story>>` and `load_story_for_space(sid, context) -> Result<Story>` following the same pattern as milestones: call `projects_for(context).story_list()` for SIDs, then `story_get(sid)` per item, map SDK type to local `Story` model. Dependencies: none (parallel with Steps 1–3). #### Step 5 — Fix `get_link_selector` for Deal and Task Files: `handlers/mod.rs` Deal branch: add `opt_link` for `instrument_sid` and `contract_sid`; load instruments and contracts from store; add `"instruments"` and `"contracts"` to `linkable_types`. Task branch: add `opt_link` for `story_sid`; load stories from store (Step 4 must be done first); add `"stories"` to `linkable_types`. Dependencies: Step 4. #### Step 6 — Fix `contacts_detail` handler Files: `handlers/mod.rs` Load `contract`, `opportunity`, `deal` from their SIDs on the contact. Pass to `ContactDetailTemplate`. Dependencies: none (parallel with Step 5). #### Step 7 — Fix `deals_detail` handler Files: `handlers/mod.rs` Load `opportunity`, `instrument`, `contract` from their SIDs on the deal. Pass to `DealDetailTemplate`. Dependencies: none. #### Step 8 — Fix `tasks_detail` handler Files: `handlers/mod.rs` Load `story` from `task.story_sid`. Pass to `TaskDetailTemplate`. Dependencies: Step 4. #### Step 9 — Fix `milestones_detail` handler Files: `handlers/mod.rs` Load `owner` (a `Person`) from `milestone.owner_sid`. Pass to `MilestoneDetailTemplate`. Dependencies: none. #### Step 10 — Fix `projects_detail` handler Files: `handlers/mod.rs` Replace `let owner: Option<Person> = None;` with a real lookup using `project.owner_sid`. Pass to `ProjectDetailTemplate`. Dependencies: none. #### Step 11 — Update ContactDetailTemplate Files: `templates/mod.rs` Add `contract: Option<Contract>`, `opportunity: Option<Opportunity>`, `deal: Option<Deal>` to struct. Add three hyperlinked rows to the HTML render. Dependencies: Step 6. #### Step 12 — Update DealDetailTemplate Files: `templates/mod.rs` Add `opportunity: Option<Opportunity>`, `instrument: Option<Instrument>`, `contract: Option<Contract>`. Add three hyperlinked rows. Dependencies: Step 7. #### Step 13 — Update TaskDetailTemplate Files: `templates/mod.rs` Add `story: Option<Story>`. Add one hyperlinked row. Dependencies: Step 8. #### Step 14 — Update MilestoneDetailTemplate Files: `templates/mod.rs` Add `owner: Option<Person>`. Add one hyperlinked row. Dependencies: Step 9. #### Step 15 — Remove dead code Files: `services/mod.rs` Delete `get_link_selector_data` method and `LinkSelectorData` struct. Verify no callers remain. Dependencies: none. #### Step 16 — Replace stale instruction docs Files: `instructions/coding_instructions.md`, `instructions/coding_instructions_ai.md` Replace heroscript/TOML-based content with accurate description of the current architecture: Axum web server backed by `hero_osis_sdk` (BusinessClient, ProjectsClient, IdentityClient per context). Dependencies: none. --- ### Acceptance Criteria - [ ] `cargo build` passes with no errors - [ ] Link selector saves actually persist to hero_osis (deal instruments/contracts, task stories) - [ ] Deal link selector shows instruments and contracts - [ ] Task link selector shows stories - [ ] Contact detail displays contract, opportunity, deal as links when set - [ ] Deal detail displays opportunity, instrument, contract as links - [ ] Task detail displays story as a link - [ ] Milestone detail displays owner as a link - [ ] Project detail displays owner as a link (not blank) - [ ] `LinkSelectorData` and `get_link_selector_data` are gone - [ ] `instructions/` docs no longer reference heroscript or TOML database --- ### Notes - `save_to_space` itself is NOT deleted — it is still called from other paths (e.g. file uploads). Leave it with its TODO comment. - The `ProjectDetailTemplate` struct already has an `owner: Option<Person>` field — only the handler and the comment need updating. - For error conversion in `update_*_links`: check whether `HeroBizError` implements `std::error::Error`; use `.map_err(anyhow::Error::from)` if it does, otherwise `.map_err(|e| anyhow::anyhow!(e.to_string()))`. - Steps 1, 2, 3, 4, 6, 7, 9, 10, 15, 16 are fully independent and can proceed in parallel. Steps 5 and 8 depend on Step 4. Steps 11–14 depend on Steps 6–9 respectively.
Author
Member

Implementation Complete

All 16 steps from the spec have been implemented and cargo build --release passes cleanly.

Changes made

crates/hero_biz_ui/src/services/mod.rs

  • Added load_all_stories, load_all_stories_for_space, load_story, load_story_for_space following the milestone pattern
  • Removed dead get_link_selector_data method and LinkSelectorData struct

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

  • Replaced the no-op save_to_space stub with real store.save_*(context, &entity).await calls in all nine update_*_links functions
  • Added instrument_sid and contract_sid wiring in update_deal_links; removed incorrect comment
  • Added story_sid wiring in update_task_links
  • Extended get_link_selector Deal branch with instruments and contracts; Task branch with stories
  • contacts_detail: loads contract, opportunity, deal from their SIDs
  • deals_detail: loads opportunity, instrument, contract from their SIDs
  • tasks_detail: loads story from task.story_sid
  • milestones_detail: loads owner (Person) from milestone.owner_sid
  • projects_detail: replaced let owner: Option<Person> = None with real lookup

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

  • ContactDetailTemplate: added contract, opportunity, deal fields and hyperlinked display rows
  • DealDetailTemplate: added opportunity, instrument, contract fields and hyperlinked display rows
  • TaskDetailTemplate: added story field and hyperlinked display row
  • MilestoneDetailTemplate: added owner field and hyperlinked display row

instructions/coding_instructions.md and instructions/coding_instructions_ai.md

  • Replaced heroscript/TOML content with accurate description of current Axum + hero_osis_sdk architecture

Build

cargo build --release — no errors, no warnings

## Implementation Complete All 16 steps from the spec have been implemented and `cargo build --release` passes cleanly. ### Changes made **`crates/hero_biz_ui/src/services/mod.rs`** - Added `load_all_stories`, `load_all_stories_for_space`, `load_story`, `load_story_for_space` following the milestone pattern - Removed dead `get_link_selector_data` method and `LinkSelectorData` struct **`crates/hero_biz_ui/src/web/handlers/mod.rs`** - Replaced the no-op `save_to_space` stub with real `store.save_*(context, &entity).await` calls in all nine `update_*_links` functions - Added `instrument_sid` and `contract_sid` wiring in `update_deal_links`; removed incorrect comment - Added `story_sid` wiring in `update_task_links` - Extended `get_link_selector` Deal branch with instruments and contracts; Task branch with stories - `contacts_detail`: loads contract, opportunity, deal from their SIDs - `deals_detail`: loads opportunity, instrument, contract from their SIDs - `tasks_detail`: loads story from `task.story_sid` - `milestones_detail`: loads owner (Person) from `milestone.owner_sid` - `projects_detail`: replaced `let owner: Option<Person> = None` with real lookup **`crates/hero_biz_ui/src/web/templates/mod.rs`** - `ContactDetailTemplate`: added `contract`, `opportunity`, `deal` fields and hyperlinked display rows - `DealDetailTemplate`: added `opportunity`, `instrument`, `contract` fields and hyperlinked display rows - `TaskDetailTemplate`: added `story` field and hyperlinked display row - `MilestoneDetailTemplate`: added `owner` field and hyperlinked display row **`instructions/coding_instructions.md`** and **`instructions/coding_instructions_ai.md`** - Replaced heroscript/TOML content with accurate description of current Axum + hero_osis_sdk architecture ### Build `cargo build --release` — no errors, no warnings
casper-stevens changed title from Link integration audit: save_to_space stub, model/selector gaps, dead code, stale docs to Bug: chat and file upload stubs non-functional; Deal/Task link selector gaps; dead code 2026-05-04 07:29:19 +00:00
Author
Member

Implementation Spec: Fix Non-Functional Chat and File Upload Stubs

Objective

Replace three no-op stubs in Store with working disk-backed implementations, and delete one unreachable dead-code function. No ChatMessage or Upload schema exists in the hero_osis_sdk business domain. The correct approach — matching how FlowStore in src/ai/flow.rs works — is to use the existing db_root PathBuf for local file storage: JSON-lines for chat history, and a namespaced directory for uploaded files.

Storage layout:

  • Chat messages: {db_root}/chat/{context_type}/{context_id}/messages.jsonl
  • Uploads: {db_root}/uploads/{context_type}/{context_id}/{uuid}_{filename}

The chat_file handler already calls state.store.db_root().join(&file_path) to serve files from disk, proving the intended design is local storage.

Requirements

  1. save_chat_message must persist the ChatMessage to disk and return its sid.
  2. load_chat_messages must read all messages for the given (context_type, context_id) pair and return them in chronological order.
  3. clear_chat_messages must actually delete the messages file for the given context.
  4. save_upload must write the file bytes to disk under a collision-safe path and return the relative path string.
  5. save_to_space (dead code, never called) must be deleted.
  6. No changes to Cargo.toml — all required crates (uuid, chrono, serde_json, std::fs) are already present.

Files to Modify

Single file: crates/hero_biz_ui/src/services/mod.rs

Implementation Plan

Step 1: Add imports

Add use std::fs; and use std::io::{BufRead, BufReader, Write}; at the top of services/mod.rs.

Step 2: Delete save_to_space

Remove the entire method (dead code, never called, anyhow::Result return type unlike the rest).

Step 3: Implement save_upload

  • Create directory {db_root}/uploads/{context_type}/{context_id}/
  • Sanitize filename (keep alphanumeric, ., -; replace everything else with _)
  • Write bytes to {uuid}_{safe_name}
  • Return relative path uploads/{context_type}/{context_id}/{uuid}_{safe_name}
  • Return Err (not silent Ok) on IO failure

Step 4: Implement save_chat_message

  • Auto-populate sid with uuid::Uuid::new_v4() if empty
  • Auto-populate timestamp with chrono::Utc::now() ISO-8601 if empty
  • Create directory {db_root}/chat/{context_type}/{context_id}/
  • Append one JSON line to messages.jsonl using OpenOptions::append(true)
  • Return the sid

Step 5: Implement load_chat_messages

  • Return Ok(Vec::new()) if messages.jsonl does not exist
  • Read line-by-line, skip blank lines, deserialize each line as ChatMessage
  • Skip and warn (via tracing::warn!) on malformed lines rather than returning an error
  • Return messages in file order (chronological)

Step 6: Implement clear_chat_messages

  • Delete messages.jsonl if it exists
  • Return Ok(()) regardless (including when file does not exist)

Acceptance Criteria

  • save_to_space is fully deleted
  • save_upload writes bytes to {db_root}/uploads/{context_type}/{context_id}/{uuid}_{filename}
  • save_upload returns Err on IO failure (not silent Ok)
  • The returned file_path from save_upload can be served via GET /api/chat/file/{file_path}
  • save_chat_message appends a JSON line to {db_root}/chat/{context_type}/{context_id}/messages.jsonl
  • save_chat_message auto-populates sid and timestamp if empty
  • load_chat_messages returns messages in chronological order
  • load_chat_messages returns Ok(Vec::new()) when no file exists yet
  • load_chat_messages skips malformed lines with a warning instead of erroring
  • clear_chat_messages deletes the file if it exists, Ok(()) either way
  • cargo build passes with no errors

Notes

  • save_to_space itself uses anyhow::Result (not the crate's Result) and is in_degree=0 — safe to delete entirely.
  • The generic save and save<T> stubs at the top of that section are NOT part of this issue — do not touch them.
  • The ChatMessage struct derives Clone so message.clone() is valid in the save implementation.
  • context_sid (field on ChatMessage) and the context_id URL parameter are the same value — use msg.context_sid for the directory path when saving, and the context_id parameter when loading.
## Implementation Spec: Fix Non-Functional Chat and File Upload Stubs ### Objective Replace three no-op stubs in `Store` with working disk-backed implementations, and delete one unreachable dead-code function. No `ChatMessage` or `Upload` schema exists in the `hero_osis_sdk` `business` domain. The correct approach — matching how `FlowStore` in `src/ai/flow.rs` works — is to use the existing `db_root` `PathBuf` for local file storage: JSON-lines for chat history, and a namespaced directory for uploaded files. Storage layout: - Chat messages: `{db_root}/chat/{context_type}/{context_id}/messages.jsonl` - Uploads: `{db_root}/uploads/{context_type}/{context_id}/{uuid}_{filename}` The `chat_file` handler already calls `state.store.db_root().join(&file_path)` to serve files from disk, proving the intended design is local storage. ### Requirements 1. `save_chat_message` must persist the `ChatMessage` to disk and return its `sid`. 2. `load_chat_messages` must read all messages for the given `(context_type, context_id)` pair and return them in chronological order. 3. `clear_chat_messages` must actually delete the messages file for the given context. 4. `save_upload` must write the file bytes to disk under a collision-safe path and return the relative path string. 5. `save_to_space` (dead code, never called) must be deleted. 6. No changes to `Cargo.toml` — all required crates (`uuid`, `chrono`, `serde_json`, `std::fs`) are already present. ### Files to Modify **Single file**: `crates/hero_biz_ui/src/services/mod.rs` ### Implementation Plan #### Step 1: Add imports Add `use std::fs;` and `use std::io::{BufRead, BufReader, Write};` at the top of `services/mod.rs`. #### Step 2: Delete `save_to_space` Remove the entire method (dead code, never called, `anyhow::Result` return type unlike the rest). #### Step 3: Implement `save_upload` - Create directory `{db_root}/uploads/{context_type}/{context_id}/` - Sanitize filename (keep alphanumeric, `.`, `-`; replace everything else with `_`) - Write bytes to `{uuid}_{safe_name}` - Return relative path `uploads/{context_type}/{context_id}/{uuid}_{safe_name}` - Return `Err` (not silent `Ok`) on IO failure #### Step 4: Implement `save_chat_message` - Auto-populate `sid` with `uuid::Uuid::new_v4()` if empty - Auto-populate `timestamp` with `chrono::Utc::now()` ISO-8601 if empty - Create directory `{db_root}/chat/{context_type}/{context_id}/` - Append one JSON line to `messages.jsonl` using `OpenOptions::append(true)` - Return the `sid` #### Step 5: Implement `load_chat_messages` - Return `Ok(Vec::new())` if `messages.jsonl` does not exist - Read line-by-line, skip blank lines, deserialize each line as `ChatMessage` - Skip and warn (via `tracing::warn!`) on malformed lines rather than returning an error - Return messages in file order (chronological) #### Step 6: Implement `clear_chat_messages` - Delete `messages.jsonl` if it exists - Return `Ok(())` regardless (including when file does not exist) ### Acceptance Criteria - [ ] `save_to_space` is fully deleted - [ ] `save_upload` writes bytes to `{db_root}/uploads/{context_type}/{context_id}/{uuid}_{filename}` - [ ] `save_upload` returns `Err` on IO failure (not silent `Ok`) - [ ] The returned `file_path` from `save_upload` can be served via `GET /api/chat/file/{file_path}` - [ ] `save_chat_message` appends a JSON line to `{db_root}/chat/{context_type}/{context_id}/messages.jsonl` - [ ] `save_chat_message` auto-populates `sid` and `timestamp` if empty - [ ] `load_chat_messages` returns messages in chronological order - [ ] `load_chat_messages` returns `Ok(Vec::new())` when no file exists yet - [ ] `load_chat_messages` skips malformed lines with a warning instead of erroring - [ ] `clear_chat_messages` deletes the file if it exists, `Ok(())` either way - [ ] `cargo build` passes with no errors ### Notes - `save_to_space` itself uses `anyhow::Result` (not the crate's `Result`) and is `in_degree=0` — safe to delete entirely. - The generic `save` and `save<T>` stubs at the top of that section are NOT part of this issue — do not touch them. - The `ChatMessage` struct derives `Clone` so `message.clone()` is valid in the save implementation. - `context_sid` (field on `ChatMessage`) and the `context_id` URL parameter are the same value — use `msg.context_sid` for the directory path when saving, and the `context_id` parameter when loading.
Author
Member

Build Results

Status: PASS

cargo build completed with no errors.

## Build Results **Status**: PASS cargo build completed with no errors.
Author
Member

Implementation Complete

All remaining critical and low-priority items from issue #10 have been implemented.

Changes

crates/hero_biz_ui/src/services/mod.rs

  • Added use std::fs; and use std::io::{BufRead, BufReader, Write}; imports
  • Deleted dead-code stub save_to_space (was never called)
  • save_upload: now writes bytes to {db_root}/uploads/{context_type}/{context_id}/{uuid}_{filename} and returns the relative path; returns Err on IO failure instead of silently succeeding
  • load_chat_messages: reads from {db_root}/chat/{context_type}/{context_id}/messages.jsonl; returns Ok(Vec::new()) when no file exists yet; skips malformed lines with a tracing::warn! instead of erroring
  • save_chat_message: auto-populates sid (UUID v4) and timestamp (ISO-8601 UTC) if empty; appends a JSON line to messages.jsonl via OpenOptions::append(true)
  • clear_chat_messages: deletes messages.jsonl if it exists; Ok(()) either way

Storage layout

{db_root}/
  chat/{context_type}/{context_id}/messages.jsonl   # one JSON object per line
  uploads/{context_type}/{context_id}/{uuid}_{name} # raw file bytes

The chat_file handler (GET /api/chat/file/*file_path) already resolves paths via db_root().join(&file_path), so uploaded files are immediately servable after this change.

Build

cargo build — no errors, no warnings.


--- ## Implementation Complete All remaining critical and low-priority items from issue #10 have been implemented. ### Changes **`crates/hero_biz_ui/src/services/mod.rs`** - Added `use std::fs;` and `use std::io::{BufRead, BufReader, Write};` imports - Deleted dead-code stub `save_to_space` (was never called) - `save_upload`: now writes bytes to `{db_root}/uploads/{context_type}/{context_id}/{uuid}_{filename}` and returns the relative path; returns `Err` on IO failure instead of silently succeeding - `load_chat_messages`: reads from `{db_root}/chat/{context_type}/{context_id}/messages.jsonl`; returns `Ok(Vec::new())` when no file exists yet; skips malformed lines with a `tracing::warn!` instead of erroring - `save_chat_message`: auto-populates `sid` (UUID v4) and `timestamp` (ISO-8601 UTC) if empty; appends a JSON line to `messages.jsonl` via `OpenOptions::append(true)` - `clear_chat_messages`: deletes `messages.jsonl` if it exists; `Ok(())` either way ### Storage layout ``` {db_root}/ chat/{context_type}/{context_id}/messages.jsonl # one JSON object per line uploads/{context_type}/{context_id}/{uuid}_{name} # raw file bytes ``` The `chat_file` handler (`GET /api/chat/file/*file_path`) already resolves paths via `db_root().join(&file_path)`, so uploaded files are immediately servable after this change. ### Build `cargo build` — no errors, no warnings. ---
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#10
No description provided.