Tickets for FE and actor split
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
# 109 — Stop binding `ottyl mcp` to one session for its whole lifetime
|
||||
|
||||
## Summary
|
||||
|
||||
`ottyl mcp --session <name>` resolves the session name to a port
|
||||
once, at process startup, and then serves stdio JSON-RPC for the
|
||||
life of the process against that fixed URL. In practice this means
|
||||
every ottyl session an agent wants to talk to needs its own
|
||||
registered MCP server entry (e.g. `ottyl-hygienic-gyrfalcon`), and
|
||||
switching which running `ottyl` instance a client is looking at
|
||||
requires killing that entry and re-registering a new one — which,
|
||||
for MCP clients that only read server config at startup, means the
|
||||
whole host process needs restarting before the new tools are even
|
||||
callable. This got hit directly: an agent needed to switch from an
|
||||
old dead session (`enormous-warthog`) to a new one
|
||||
(`hygienic-gyrfalcon`) mid-conversation and had no way to do it
|
||||
without editing MCP config and waiting for a restart.
|
||||
|
||||
## Where
|
||||
|
||||
- `src/cli/mod.rs:43-49` — `Command::Mcp { session: Option<String>,
|
||||
port: Option<u16> }`, the CLI surface.
|
||||
- `src/cli/mod.rs:171-195` (`run_mcp`) — resolves `session` to a port
|
||||
via `sessions::resolve_session` exactly once, builds one
|
||||
`OttylMcp::new(base_url)`, then blocks forever on
|
||||
`service.waiting()`. There is no re-resolution after startup.
|
||||
- `src/api/mcp_server.rs:254-282` — `OttylMcp` stores `base_url:
|
||||
String` as a plain field set once in `new()`; every `#[tool]`
|
||||
method builds request URLs via `self.url(path)`
|
||||
(`mcp_server.rs:282-284`), so the fixed base URL is threaded
|
||||
through every single tool call.
|
||||
- `src/cli/sessions.rs` — `SessionInfo { name, port, pid }` and
|
||||
`resolve_session`/`list_sessions`, the registry this would need to
|
||||
re-query if the bound session can change after startup.
|
||||
|
||||
## Direction
|
||||
|
||||
Two ways to remove the "pick a session forever at startup" behavior;
|
||||
pick one during implementation rather than building both:
|
||||
|
||||
1. **Request/response CLI, no long-running process.** Add a CLI
|
||||
subcommand (e.g. `ottyl call <session> <tool> <json-args>`) that
|
||||
resolves the session, makes the one HTTP call, prints the result,
|
||||
and exits — no `rmcp`/stdio JSON-RPC server at all. Simplest fix
|
||||
for the "MCP config never changes, session does" mismatch, but
|
||||
loses whatever the MCP client gives you over a bare CLI call
|
||||
(tool discovery/schema, a persistent connection) — check whether
|
||||
that matters to how this is actually invoked before going this
|
||||
route.
|
||||
2. **Long-running MCP server, session switchable at runtime.** Keep
|
||||
`ottyl mcp` as a stdio server, but stop fixing `base_url` at
|
||||
construction: either (a) accept a `session` (or `port`) argument
|
||||
on every tool call, resolved against `sessions::resolve_session`
|
||||
per-call instead of once in `run_mcp`, with the constructor's
|
||||
`--session`/`--port` becoming just a default; or (b) add an
|
||||
explicit `switch_session` tool that mutates the stored target
|
||||
(`base_url` would need to move from a plain field to something
|
||||
mutable, e.g. `Mutex<String>` or resolved fresh from the registry
|
||||
on every call from a stored session name instead of a
|
||||
pre-resolved URL).
|
||||
|
||||
## Scope
|
||||
|
||||
- Land one of the two directions above for `ottyl mcp` /
|
||||
`src/api/mcp_server.rs`; out of scope to build both.
|
||||
- If going with direction 2, update `SKILL.md`'s "Connect an MCP
|
||||
client" section, since it currently documents the
|
||||
resolve-once-at-startup model.
|
||||
- Out of scope: changing anything about the underlying `/api/...`
|
||||
HTTP routes themselves, or the session registry format in
|
||||
`src/cli/sessions.rs` — this is purely about how the MCP-facing
|
||||
process picks and holds (or doesn't hold) its target session.
|
||||
@@ -0,0 +1,72 @@
|
||||
# 110 — Clicking a legend entry should focus that series, fading the rest
|
||||
|
||||
## Summary
|
||||
|
||||
`PanelChart`'s line and pie charts render a recharts `<Legend>`
|
||||
(`webapp/src/components/PanelChart.tsx:134`, `:190`) but it's purely
|
||||
decorative — clicking an entry does nothing. Bar charts have no
|
||||
`<Legend>` at all (`:151-168`), since each bar is its own labeled
|
||||
category on the x-axis rather than a separate series.
|
||||
|
||||
Add click-to-focus: clicking a legend entry marks that series
|
||||
"focused"; any focused series stay at full opacity/stroke while every
|
||||
other series fades. Clicking the same entry again (or clicking empty
|
||||
space in the legend, per whatever recharts exposes) clears focus and
|
||||
returns everyone to equal opacity — i.e. "no focused series" means
|
||||
"all at the same opacity", not "all faded".
|
||||
|
||||
## Where
|
||||
|
||||
- `webapp/src/components/PanelChart.tsx:116-148` — line chart. Series
|
||||
are recharts `<Line>` elements keyed by `keys` (from `toLineData`,
|
||||
`:60-78`), colored via `COLORS[i % COLORS.length]` (`:140`). Fading
|
||||
a line means varying `strokeOpacity` (or swapping `stroke` for a
|
||||
`color-mix`'d faded variant) per key based on focus state.
|
||||
- `webapp/src/components/PanelChart.tsx:170-193` — pie chart. Slices
|
||||
are `<Cell>` elements (`:179-181`) keyed by row index into `rows`
|
||||
(from `squashRows`, `:50-57`); fading a slice means varying its
|
||||
`fillOpacity`.
|
||||
- `webapp/src/components/PanelChart.tsx:134`, `:190` — the two
|
||||
`<Legend>` usages. recharts' `<Legend>` takes an `onClick` handler
|
||||
on its payload entries (`(entry) => void`, where `entry.dataKey` /
|
||||
`entry.value` identifies which series was clicked) — this is the
|
||||
hook point rather than building a custom legend from scratch.
|
||||
- Bar charts (`:151-168`) have no legend to click, since bars aren't
|
||||
named series here — out of scope for this ticket unless the
|
||||
direction chosen below turns out to need bar-chart parity (see
|
||||
Scope).
|
||||
|
||||
## Direction
|
||||
|
||||
- Add focus state as a `Set<string>` (or single nullable string, if
|
||||
only one series can be focused at a time — decide during
|
||||
implementation) of series keys, local to `PanelChart` via
|
||||
`useState`. Reset it when `series` changes identity to avoid stale
|
||||
focus surviving a query change onto a chart with different series.
|
||||
- Wire the `<Legend>`'s `onClick` to toggle membership in that set.
|
||||
- Compute per-series opacity from the set: full opacity if the set is
|
||||
empty or the series is a member, faded (e.g. `0.25`) otherwise.
|
||||
Thread that into `<Line stroke=...>` / `<Cell fill=...>` via
|
||||
`strokeOpacity`/`fillOpacity` rather than swapping the base color,
|
||||
so the existing `COLORS` palette and its `color-mix` theming stay
|
||||
untouched.
|
||||
- The legend entries themselves should visually reflect focus too
|
||||
(e.g. dim the faded entries' swatch/text) so it's clear what's
|
||||
focused without hunting across the plot — recharts' legend
|
||||
`formatter` prop can restyle each entry's label.
|
||||
|
||||
## Scope
|
||||
|
||||
- Line and pie charts only, matching where `<Legend>` already
|
||||
renders. Bar charts stay as-is — no legend, no per-bar fade — unless
|
||||
investigation shows recharts' bar `<Legend>` support is trivial to
|
||||
add alongside this; if so, note it as a follow-up rather than
|
||||
folding scope creep into this ticket.
|
||||
- Out of scope: persisting focus state across re-renders driven by
|
||||
polling (`Dashboards.tsx`'s `PanelPreview`, `:46-92`, re-fetches on
|
||||
every poll tick) — focus should survive a data refresh as long as
|
||||
the series identity (legend key) is unchanged, but there's no need
|
||||
to persist it to `localStorage` or across a page reload.
|
||||
- Out of scope: multi-select "compare exactly these two series" UX
|
||||
beyond simple toggle-membership — keep it to click-to-add/click-to-
|
||||
remove from the focused set.
|
||||
@@ -0,0 +1,51 @@
|
||||
# 111 — Legend must scroll instead of overflowing the panel
|
||||
|
||||
## Summary
|
||||
|
||||
`PanelChart`'s recharts `<Legend>` (`webapp/src/components/
|
||||
PanelChart.tsx:134`, `:190`) has no height constraint or scroll
|
||||
container. A panel with many series (e.g. a high-cardinality PromQL
|
||||
query, or the post-`squashRows` `MAX_KEPT_SERIES` = 7 kept rows plus
|
||||
"Other") wraps the legend across as many lines as it needs, growing
|
||||
the panel's rendered height and pushing surrounding dashboard layout
|
||||
around rather than staying within the chart's fixed footprint.
|
||||
|
||||
## Where
|
||||
|
||||
- `webapp/src/components/PanelChart.tsx:117`, `:154`, `:176` — each
|
||||
chart type wraps its `<ResponsiveContainer>` at a fixed `height={220}`,
|
||||
but the `<Legend>` inside it (`:134`, `:190`) isn't given its own
|
||||
bounded, scrollable region — recharts lays it out in the space left
|
||||
after the plot area, which can push the container's effective
|
||||
rendered height past 220 when the legend wraps to multiple rows.
|
||||
- `webapp/src/components/Dashboards.tsx:46-92` (`PanelPreview`) is the
|
||||
only caller of `PanelChart` today — worth checking that whatever
|
||||
fixed-height/scroll wrapper gets added here doesn't fight with
|
||||
`PanelPreview`'s own loading/error states (`:85-90`), which render
|
||||
outside `PanelChart` at a different fixed height (`h-48`).
|
||||
|
||||
## Direction
|
||||
|
||||
- recharts' `<Legend>` supports a `wrapperStyle` prop (already used
|
||||
for `fontSize`, `:134`/`:190`) — extending it with a fixed
|
||||
`maxHeight` and `overflowY: 'auto'` is the natural fix and avoids
|
||||
building a custom legend renderer just for this.
|
||||
- Verify this doesn't conflict with the click-to-focus work in
|
||||
[[110-legend-click-to-focus-series]] — that ticket also touches the
|
||||
`<Legend>` props (`onClick`, `formatter`) on these same two call
|
||||
sites, so land whichever lands second by rebasing onto the other's
|
||||
`<Legend>` props rather than both independently restructuring it.
|
||||
- Decide a concrete max height (e.g. two or three legend rows' worth)
|
||||
that keeps the panel's total footprint predictable in a dashboard
|
||||
grid; check `Dashboards.tsx`'s panel grid/card layout for whatever
|
||||
existing panel height convention should inform the number.
|
||||
|
||||
## Scope
|
||||
|
||||
- Line and pie charts only, matching where `<Legend>` already
|
||||
renders — same boundary as [[110-legend-click-to-focus-series]].
|
||||
- Out of scope: virtualizing the legend list for very large series
|
||||
counts — `squashRows` (`PanelChart.tsx:50-57`) already caps bar/pie
|
||||
series at `MAX_KEPT_SERIES` (7) + "Other", and line charts have no
|
||||
equivalent cap today, but adding one is a separate concern from
|
||||
making the legend scrollable.
|
||||
@@ -0,0 +1,80 @@
|
||||
# 112 — Let a single dashboard panel pop out into its own window
|
||||
|
||||
## Summary
|
||||
|
||||
There's already a pop-out mechanism for whole primary sections (log,
|
||||
params, metrics, dashboards) — `PopOutButton` opens a new tab at
|
||||
`?section=<id>` and `App.tsx` reads that query param at startup to
|
||||
render a standalone, full-page version of just that section (see
|
||||
`webapp/src/components/CollapsibleSection.tsx:193-232` and
|
||||
`webapp/src/App.tsx:129-140`, `:372-376`). That grain is a whole
|
||||
section, not a single panel — the Dashboards section can only be
|
||||
popped out in its entirety (all panels of the active dashboard, or
|
||||
all dashboards stacked via `AllDashboardsView`,
|
||||
`Dashboards.tsx:107-135`). This ticket adds the finer-grained version:
|
||||
popping out one panel from one dashboard into its own window,
|
||||
independent of the rest of that dashboard.
|
||||
|
||||
## Where
|
||||
|
||||
- `webapp/src/components/Dashboards.tsx:46-92` (`PanelPreview`) is
|
||||
the component that would need to render standalone, full-page, the
|
||||
same way `StandaloneLog`/`StandaloneParams`/`StandaloneRanged` do
|
||||
today for whole sections (referenced in `App.tsx:373-376`, exact
|
||||
component locations elsewhere in `Dashboards.tsx`/`Metrics.tsx` per
|
||||
the skeleton read during this ticket's writeup — re-read
|
||||
`Dashboards.tsx` in full before implementing, since it was read via
|
||||
repowise's skeleton view here and several ranges (`:1-45`, `:136-
|
||||
434`) were elided).
|
||||
- `webapp/src/components/CollapsibleSection.tsx:193-206`
|
||||
(`PopOutSectionId`, `popOutUrl`) — the existing section-grain
|
||||
pop-out routing. A panel-grain pop-out needs to identify a specific
|
||||
`(dashboardId, panelId)` pair in the URL, not just a section name —
|
||||
either extend this query-param scheme with `dashboardId`/`panelId`
|
||||
params alongside `section=dashboards`, or give panels their own
|
||||
distinct query shape if reusing `PopOutSectionId` doesn't fit
|
||||
cleanly.
|
||||
- `webapp/src/App.tsx:129-140` (`parsePopOutQuery`), `:372-376` (the
|
||||
dispatch on parsed `section`) — where the new panel-grain case needs
|
||||
a branch, reading whatever new params are added above.
|
||||
- Each `Panel` (`webapp/src/lib/api.ts`'s `Panel` type, imported at
|
||||
`Dashboards.tsx:19`) needs a stable identifier already, or one to
|
||||
add, so a popped-out URL can point back at "this exact panel of
|
||||
this exact dashboard" — check whether `Panel` already carries an
|
||||
`id` field before inventing a new addressing scheme.
|
||||
|
||||
## Direction
|
||||
|
||||
- Mirror the existing section pop-out exactly: a small pop-out button
|
||||
per panel (using or extending `PopOutButton`,
|
||||
`CollapsibleSection.tsx:209-232`) that opens `?section=dashboards&
|
||||
dashboardId=<id>&panelId=<id>`, plus range params the same way
|
||||
`popOutUrl` already carries over a rolling preset or fixed range
|
||||
(`CollapsibleSection.tsx:196-206`).
|
||||
- The standalone page for this case renders just that one panel's
|
||||
`PanelPreview` (or a thin wrapper around it) full-page — no
|
||||
dashboard chrome, no other panels — analogous to how
|
||||
`StandaloneRanged` (`App.tsx:179-...`) drives `LogTimeline`'s
|
||||
picker UI on its own poll cadence for the metrics/dashboards
|
||||
standalone views.
|
||||
- The popped-out panel window should keep polling independently
|
||||
(same `POLL_MS` pattern `PanelPreview` already uses,
|
||||
`Dashboards.tsx:52-55`) rather than freezing at open time.
|
||||
|
||||
## Scope
|
||||
|
||||
- One panel popping into a new browser tab/window, matching the
|
||||
existing section pop-out's mechanism (new tab via `window.open`,
|
||||
not an in-app floating/detached window widget) — stay consistent
|
||||
with the pattern already established for log/params/metrics/
|
||||
dashboards rather than introducing a second, different pop-out
|
||||
mechanism.
|
||||
- Out of scope: editing a panel's query/chart type from the popped-out
|
||||
view — read-only preview only, matching what pop-out gives you for
|
||||
the other sections today.
|
||||
- Out of scope: [[110-legend-click-to-focus-series]] and
|
||||
[[111-scrollable-panel-legend]] — this ticket is purely about the
|
||||
window/routing mechanism; the popped-out panel should get those
|
||||
legend behaviors for free once they land in `PanelChart`, since
|
||||
it's the same component, but implementing them isn't this ticket's
|
||||
job.
|
||||
Reference in New Issue
Block a user