Item surfaces overhaul: actionable per-item page (reverse-chrono feed, compress repeats, pagination, search, filters) + directory sort/hide-untraded #92

Closed
opened 2026-07-07 06:11:11 +00:00 by coilysiren · 2 comments
Owner

Overhaul both item surfaces so they answer a holder's or buyer's actual questions. Filed from a batch of eco-app feedback Kai handed the director surface. Absorbs and supersedes #85 (item pages miss info they pivot on elsewhere).

Per-item page (frontend/src/pages/Item.tsx, frontend/src/lib/itemsApi.ts, src/eco_mcp_app/items.py)

The page today is two separate "Trades" and "Made" lists, each day {Math.floor(day)}, capped at 200 rows, no search, no pagination, no top-level summary beyond a pill. Kai wants it to revolve around actionable, reverse-chronological info that someone holding or wanting the item cares about.

  • Actionable top-level component. Above the feeds, add a card that answers: who can make it (crafters, ranked by how much they have made), how many are available right now (open sell offers / stock across stores), and who is buying (open buy orders). This is the "should I craft this, where do I buy it, who do I sell it to" summary. Pull supply/demand from the same logistics/stores/market spine /trade uses (logistics.py, stores.py, market.py).
  • Merge crafted + trades into one reverse-chrono feed. Replace the two separate lists with a single timeline, newest first, that interleaves craft events and trades. Each row reads as relative time: "someone bought a Steam Truck 2 hours ago", "Reihtnog crafted 4 Mortar 30 minutes ago". The data already carries time (seconds) and day (float) per row, so compute "X minutes / hours / days ago" against the world clock (the /info snapshot carries TimeSinceStart). Every place that shows a day must also show the hour, never a bare floored day.
  • Compress consecutive repeats. "Rechim crafted 1 Hewn Log at Carpentry Table" printed 100 times in a row collapses to one row with a count, e.g. "Rechim crafted 100 Hewn Log at Carpentry Table (over N minutes)". Collapse a run of identical (actor, verb, item, station) rows.
  • Pagination. This page lacks it most glaringly. Page the merged feed. Server cap today is MAX_PIVOT_TRADES / MAX_PIVOT_CRAFTS = 500 each. Raise or page the backend so pagination is real, not just slicing 200 client-side.
  • Free-text search over the feed (actor, item, station).
  • Structured filters. Let a viewer narrow by actor and by event type, e.g. "all Mortar crafted by Reihtnog". Deep-linkable via query params like the existing ?q= on /items.
  • Data bug: ?item=StumpLatrineItem lists only 2 people who each "made 30+". Investigate whether this is real repeated crafting (which the compress-repeats change will make legible) or a parse/dedupe defect in _fetch_craft_events / parse_craft_events (row cap MAX_ROWS_PER_ACTION, the batched re-stream, or double-counting across CRAFT_ACTION_TYPES). Name the finding in the PR.

Items directory (frontend/src/pages/Items.tsx, build_item_index in items.py)

  • Sort buttons: let the viewer sort the table by Trades, Trade volume, or Crafted. Today it is fixed-ranked by total activity.
  • Default to hiding untraded items. Items with tradeCount === 0 are hidden by default, with a toggle to show them. Most of the directory is craft-only noise for someone shopping.

Done when

Both pages carry the new structure, the merged feed compresses repeats and reads in relative time with hours, pagination + search + structured filters work on the item page, the directory sorts and hides-untraded-by-default, and the StumpLatrine finding is stated. ward exec test + lint gates green. Update docs/FEATURES.md for the reshaped item surfaces.

Overhaul both item surfaces so they answer a holder's or buyer's actual questions. Filed from a batch of eco-app feedback Kai handed the director surface. Absorbs and supersedes #85 (item pages miss info they pivot on elsewhere). ## Per-item page (`frontend/src/pages/Item.tsx`, `frontend/src/lib/itemsApi.ts`, `src/eco_mcp_app/items.py`) The page today is two separate "Trades" and "Made" lists, each `day {Math.floor(day)}`, capped at 200 rows, no search, no pagination, no top-level summary beyond a pill. Kai wants it to revolve around actionable, reverse-chronological info that someone holding or wanting the item cares about. - **Actionable top-level component.** Above the feeds, add a card that answers: who can make it (crafters, ranked by how much they have made), how many are available right now (open sell offers / stock across stores), and who is buying (open buy orders). This is the "should I craft this, where do I buy it, who do I sell it to" summary. Pull supply/demand from the same logistics/stores/market spine `/trade` uses (`logistics.py`, `stores.py`, `market.py`). - **Merge crafted + trades into one reverse-chrono feed.** Replace the two separate lists with a single timeline, newest first, that interleaves craft events and trades. Each row reads as relative time: "someone bought a Steam Truck 2 hours ago", "Reihtnog crafted 4 Mortar 30 minutes ago". The data already carries `time` (seconds) and `day` (float) per row, so compute "X minutes / hours / days ago" against the world clock (the `/info` snapshot carries `TimeSinceStart`). Every place that shows a day must also show the hour, never a bare floored day. - **Compress consecutive repeats.** "Rechim crafted 1 Hewn Log at Carpentry Table" printed 100 times in a row collapses to one row with a count, e.g. "Rechim crafted 100 Hewn Log at Carpentry Table (over N minutes)". Collapse a run of identical (actor, verb, item, station) rows. - **Pagination.** This page lacks it most glaringly. Page the merged feed. Server cap today is `MAX_PIVOT_TRADES` / `MAX_PIVOT_CRAFTS` = 500 each. Raise or page the backend so pagination is real, not just slicing 200 client-side. - **Free-text search** over the feed (actor, item, station). - **Structured filters.** Let a viewer narrow by actor and by event type, e.g. "all Mortar crafted by Reihtnog". Deep-linkable via query params like the existing `?q=` on `/items`. - **Data bug: `?item=StumpLatrineItem`** lists only 2 people who each "made 30+". Investigate whether this is real repeated crafting (which the compress-repeats change will make legible) or a parse/dedupe defect in `_fetch_craft_events` / `parse_craft_events` (row cap `MAX_ROWS_PER_ACTION`, the batched re-stream, or double-counting across `CRAFT_ACTION_TYPES`). Name the finding in the PR. ## Items directory (`frontend/src/pages/Items.tsx`, `build_item_index` in `items.py`) - **Sort buttons**: let the viewer sort the table by Trades, Trade volume, or Crafted. Today it is fixed-ranked by total activity. - **Default to hiding untraded items.** Items with `tradeCount === 0` are hidden by default, with a toggle to show them. Most of the directory is craft-only noise for someone shopping. ## Done when Both pages carry the new structure, the merged feed compresses repeats and reads in relative time with hours, pagination + search + structured filters work on the item page, the directory sorts and hides-untraded-by-default, and the StumpLatrine finding is stated. `ward exec` test + lint gates green. Update `docs/FEATURES.md` for the reshaped item surfaces.
Owner

🔒 Reserved by ward agent --driver claude — container engineer-claude-eco-app-92 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-07T06:12:57Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

run seed context — what this run is carrying (ward#609)
  • Resolved: coilyco-gaming/eco-app#92 · branch issue-92 · driver claude · workflow direct-main
  • Run: engineer-claude-eco-app-92 · ward v0.419.0 · dispatched 2026-07-07T06:12:57Z
  • Comment thread: 0 included in the pre-flight read, 0 stripped (ward's own automated comments).

Issue body as seeded:

Overhaul both item surfaces so they answer a holder's or buyer's actual questions. Filed from a batch of eco-app feedback Kai handed the director surface. Absorbs and supersedes #85 (item pages miss info they pivot on elsewhere).

## Per-item page (`frontend/src/pages/Item.tsx`, `frontend/src/lib/itemsApi.ts`, `src/eco_mcp_app/items.py`)

The page today is two separate "Trades" and "Made" lists, each `day {Math.floor(day)}`, capped at 200 rows, no search, no pagination, no top-level summary beyond a pill. Kai wants it to revolve around actionable, reverse-chronological info that someone holding or wanting the item cares about.

- **Actionable top-level component.** Above the feeds, add a card that answers: who can make it (crafters, ranked by how much they have made), how many are available right now (open sell offers / stock across stores), and who is buying (open buy orders). This is the "should I craft this, where do I buy it, who do I sell it to" summary. Pull supply/demand from the same logistics/stores/market spine `/trade` uses (`logistics.py`, `stores.py`, `market.py`).
- **Merge crafted + trades into one reverse-chrono feed.** Replace the two separate lists with a single timeline, newest first, that interleaves craft events and trades. Each row reads as relative time: "someone bought a Steam Truck 2 hours ago", "Reihtnog crafted 4 Mortar 30 minutes ago". The data already carries `time` (seconds) and `day` (float) per row, so compute "X minutes / hours / days ago" against the world clock (the `/info` snapshot carries `TimeSinceStart`). Every place that shows a day must also show the hour, never a bare floored day.
- **Compress consecutive repeats.** "Rechim crafted 1 Hewn Log at Carpentry Table" printed 100 times in a row collapses to one row with a count, e.g. "Rechim crafted 100 Hewn Log at Carpentry Table (over N minutes)". Collapse a run of identical (actor, verb, item, station) rows.
- **Pagination.** This page lacks it most glaringly. Page the merged f

… (truncated to 2000 chars; full body is on this issue)

Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.419.0).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-eco-app-92` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-07T06:12:57Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <details><summary>run seed context — what this run is carrying (ward#609)</summary> - **Resolved:** `coilyco-gaming/eco-app#92` · branch `issue-92` · driver `claude` · workflow `direct-main` - **Run:** `engineer-claude-eco-app-92` · ward `v0.419.0` · dispatched `2026-07-07T06:12:57Z` - **Comment thread:** 0 included in the pre-flight read, 0 stripped (ward's own automated comments). **Issue body as seeded:** ``` Overhaul both item surfaces so they answer a holder's or buyer's actual questions. Filed from a batch of eco-app feedback Kai handed the director surface. Absorbs and supersedes #85 (item pages miss info they pivot on elsewhere). ## Per-item page (`frontend/src/pages/Item.tsx`, `frontend/src/lib/itemsApi.ts`, `src/eco_mcp_app/items.py`) The page today is two separate "Trades" and "Made" lists, each `day {Math.floor(day)}`, capped at 200 rows, no search, no pagination, no top-level summary beyond a pill. Kai wants it to revolve around actionable, reverse-chronological info that someone holding or wanting the item cares about. - **Actionable top-level component.** Above the feeds, add a card that answers: who can make it (crafters, ranked by how much they have made), how many are available right now (open sell offers / stock across stores), and who is buying (open buy orders). This is the "should I craft this, where do I buy it, who do I sell it to" summary. Pull supply/demand from the same logistics/stores/market spine `/trade` uses (`logistics.py`, `stores.py`, `market.py`). - **Merge crafted + trades into one reverse-chrono feed.** Replace the two separate lists with a single timeline, newest first, that interleaves craft events and trades. Each row reads as relative time: "someone bought a Steam Truck 2 hours ago", "Reihtnog crafted 4 Mortar 30 minutes ago". The data already carries `time` (seconds) and `day` (float) per row, so compute "X minutes / hours / days ago" against the world clock (the `/info` snapshot carries `TimeSinceStart`). Every place that shows a day must also show the hour, never a bare floored day. - **Compress consecutive repeats.** "Rechim crafted 1 Hewn Log at Carpentry Table" printed 100 times in a row collapses to one row with a count, e.g. "Rechim crafted 100 Hewn Log at Carpentry Table (over N minutes)". Collapse a run of identical (actor, verb, item, station) rows. - **Pagination.** This page lacks it most glaringly. Page the merged f ``` … (truncated to 2000 chars; full body is on this issue) Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.419.0). </details> <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - actionable per-item page (summary + merged compressed reverse-chrono feed with search/actor/type filters + pagination) and directory sort chips + hide-untraded-by-default landed on main.

Honest retrospective:

The shape of this one was friendlier than expected because the hard parts already existed. The supply/demand "who buys it, where's it cheapest" summary is just fetch_logistics(item=...) folded into the pivot — the logistics spine /trade uses was right there, so I reused it rather than re-deriving offers. The feed compression (collapse runs of identical actor/verb/station rows into one counted row) fell out cleanly as a pure function, which made it pleasant to unit-test.

What fought back: the world clock. The issue says age events against /info's TimeSinceStart, but mid-run I merged main and discovered #93 had just landed a whole two-calendars problem — the exporter CSVs count days as Time/86400 while the /info world clock counts 3600s/day. #93 also shipped exactly the helper item surfaces were meant to consume (formatRelative), so I switched the feed onto it. That merge conflict in format.ts was the only real friction, and main moved twice under me (concurrent containers), so I re-merged twice.

StumpLatrine finding: real, not a bug. I queried the live deploy — ?item=StumpLatrineItem is two genuine bulk-craft rows (Count 40 + 32 at a Workbench), one row per bulk craft. craftCount (events) vs craftQuantity (units) diverging is the correct, now-legible behavior; no parse/dedupe defect in _fetch_craft_events.

Confidence: high on the logic (491 backend + 98 frontend tests green, lint/build clean, and I smoke-tested the real payload shape end-to-end). One rough edge worth a follow-up: supply/demand totals are a floor when a market has more stores than the top-N offers shown (flagged with a capped bool), and the exporter-vs-world-clock day reconciliation #93 flagged is still a site-wide follow-up, not closed here.

WARD-OUTCOME: done - actionable per-item page (summary + merged compressed reverse-chrono feed with search/actor/type filters + pagination) and directory sort chips + hide-untraded-by-default landed on main. Honest retrospective: The shape of this one was friendlier than expected because the hard parts already existed. The supply/demand "who buys it, where's it cheapest" summary is just `fetch_logistics(item=...)` folded into the pivot — the logistics spine `/trade` uses was right there, so I reused it rather than re-deriving offers. The feed compression (collapse runs of identical actor/verb/station rows into one counted row) fell out cleanly as a pure function, which made it pleasant to unit-test. What fought back: the world clock. The issue says age events against `/info`'s `TimeSinceStart`, but mid-run I merged main and discovered #93 had just landed a whole two-calendars problem — the exporter CSVs count days as Time/86400 while the /info world clock counts 3600s/day. #93 also shipped exactly the helper item surfaces were meant to consume (`formatRelative`), so I switched the feed onto it. That merge conflict in `format.ts` was the only real friction, and main moved twice under me (concurrent containers), so I re-merged twice. StumpLatrine finding: **real, not a bug.** I queried the live deploy — `?item=StumpLatrineItem` is two genuine bulk-craft rows (Count 40 + 32 at a Workbench), one row per bulk craft. `craftCount` (events) vs `craftQuantity` (units) diverging is the correct, now-legible behavior; no parse/dedupe defect in `_fetch_craft_events`. Confidence: high on the logic (491 backend + 98 frontend tests green, lint/build clean, and I smoke-tested the real payload shape end-to-end). One rough edge worth a follow-up: supply/demand totals are a floor when a market has more stores than the top-N offers shown (flagged with a `capped` bool), and the exporter-vs-world-clock day reconciliation #93 flagged is still a site-wide follow-up, not closed here.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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
coilyco-gaming/eco-app#92
No description provided.