diff --git a/.issueflows/03-solved-issues/issue177_original.md b/.issueflows/03-solved-issues/issue177_original.md new file mode 100644 index 0000000..d197150 --- /dev/null +++ b/.issueflows/03-solved-issues/issue177_original.md @@ -0,0 +1,7 @@ +# Issue #177: gui color scheme and layout + +Source: https://github.com/cellpy/cellpy-simple-gui/issues/177 + +## Original issue text + +Not too happy with the color scheme and layout of the app. Suggest several modern layouts. Pick one and implement diff --git a/.issueflows/03-solved-issues/issue177_plan.md b/.issueflows/03-solved-issues/issue177_plan.md new file mode 100644 index 0000000..6a46c95 --- /dev/null +++ b/.issueflows/03-solved-issues/issue177_plan.md @@ -0,0 +1,84 @@ +# Plan — Issue #177: GUI color scheme and layout + +Source: https://github.com/cellpy/cellpy-simple-gui/issues/177 + +## Goal + +Refresh the app chrome (colors, type, spacing, control density) so the window +looks current, while keeping the same screens: left journal, plot tabs, chart. + +## Constraints + +- Frontend only. No API, job, or cellpy changes. +- Keep the DOM regions tests already read: `.sidebar`, the Data hint, `.main`, + `.tabs`. `tests/test_api.py` checks sidebar hint text, not pixels. +- Light and dark both move. Dark stays the default. Theme toggle and + `localStorage` key `csg-theme` stay. +- Plot **figure theme** and **Colors** (cellpy / safe / muted / …) stay as they + are. Categorical vs gradient is [#176](https://github.com/cellpy/cellpy-simple-gui/issues/176). +- Same information architecture unless the chosen option below says otherwise. +- README screenshots (`docs/img/`) go stale. Update them in this issue only if + you ask; otherwise a follow-up. + +### Prior art + +- Theme tokens and the two-column grid live in + [`src/cellpy_simple_gui/web/static/css/app.css`](../../../src/cellpy_simple_gui/web/static/css/app.css) + (`:root[data-theme]`, `.layout` = `330px 1fr`, `.topbar`, `.tabs`). +- Shell markup is + [`src/cellpy_simple_gui/web/templates/index.html`](../../../src/cellpy_simple_gui/web/templates/index.html) + (top bar, sidebar Project / Data / cells, main tabs). +- Toggle is `toggleTheme()` in + [`src/cellpy_simple_gui/web/static/js/app.js`](../../../src/cellpy_simple_gui/web/static/js/app.js). + Coexist: do not replace that mechanism. +- Closed #32 / #36 / #37 already own figure theme and plot color schemes. + Mirror their boundary: app chrome here, plot chrome there. +- Toolbox: none. Graph: community "layout" is plotly figure layout, not this UI. + +## Approach + +Three layouts. **Recommended: A.** B and C rebuild the shell. + +### A — Quiet lab (recommended) + +Keep top bar + 330px sidebar + plot tabs. Change the paint and the density. + +- Drop the body radial gradient and the blue→teal gradient on primary buttons. + One accent (teal `#1f8f6b`, same family as the README badge). Neutral panels. +- Solid top bar (the transparent bar was so the gradient showed through). +- Slightly smaller radius, calmer borders, no uppercase panel titles. +- Chart controls wrap in one compact row; the chart keeps the rest of the column. +- Sidebar width stays 330px so Project / Data fields do not reflow. + +### B — Plot-first + +Sidebar collapses to an icon rail. Project, Data, and the cell list open in a +drawer. Tabs move into the top bar. The chart fills the window. Daily cell +selection takes an extra click. + +### C — Top strip + +No left sidebar. Project actions and the cell list become a horizontal strip +under the top bar. Tabs and the chart sit below. Wide plots; a long cell list +scrolls sideways. + +Implement only the option you accept. A is CSS plus small class tweaks in the +template. B or C also move blocks in `index.html` and the layout rules in +`app.js` only if a click target moves. + +## Files to touch + +- `src/cellpy_simple_gui/web/static/css/app.css` — tokens, top bar, panels, buttons, tabs, control row. +- `src/cellpy_simple_gui/web/templates/index.html` — class hooks for the compact control row (A), or moved blocks (B/C). +- `src/cellpy_simple_gui/web/static/js/app.js` — only if B adds a drawer open/close flag. + +## Test strategy + +`uv run pytest` from the worktree. No new pixel test. Existing API HTML checks +must still pass. After the change, open `./run` and check dark and light on +Cycle summary, Cycles, and Cell explorer, plus one modal (Add cells). + +## Open questions + +1. **Which layout?** A (recommended), B, or C. +2. **README screenshots** in this issue, or later? diff --git a/.issueflows/03-solved-issues/issue177_status.md b/.issueflows/03-solved-issues/issue177_status.md new file mode 100644 index 0000000..664bd5e --- /dev/null +++ b/.issueflows/03-solved-issues/issue177_status.md @@ -0,0 +1,15 @@ +# Status — Issue #177 + +- [x] Done + +## What's done + +- 2026-10-03 — Accepted layout **A (Quiet lab)**. Screenshots are in scope. +- Chrome in `app.css`: neutral tokens, solid top bar, flat teal primary, compact controls. +- Figure theme tokens and the desktop window background follow the same neutrals, so Match app does not leave a blue plot in a gray shell. +- Recaptured `docs/img` (PNG gallery and `demo.gif`) against that chrome. Light shot uses Group avg + Spread. +- Regenerated `llms-full.txt` so it matches the README heading (`Some examples`). `uv run --extra dev pytest`: the previous `test_llms_full_txt_is_current` failure is gone; `tests/test_agent_docs.py` passes. Full suite before that regen was 357 passed, 6 skipped, 1 failed. + +## Remaining work + +- None. diff --git a/docs/img/add_cells_raw.png b/docs/img/add_cells_raw.png index 859bd68..ab0f284 100644 Binary files a/docs/img/add_cells_raw.png and b/docs/img/add_cells_raw.png differ diff --git a/docs/img/add_cells_staged.png b/docs/img/add_cells_staged.png index 5fa90f3..faf2f21 100644 Binary files a/docs/img/add_cells_staged.png and b/docs/img/add_cells_staged.png differ diff --git a/docs/img/cell_collect.png b/docs/img/cell_collect.png index 530888e..26cd314 100644 Binary files a/docs/img/cell_collect.png and b/docs/img/cell_collect.png differ diff --git a/docs/img/cell_dqdv.png b/docs/img/cell_dqdv.png index 78d294e..0f5fa77 100644 Binary files a/docs/img/cell_dqdv.png and b/docs/img/cell_dqdv.png differ diff --git a/docs/img/cycles_per_cell.png b/docs/img/cycles_per_cell.png index 81d36ca..d9a6419 100644 Binary files a/docs/img/cycles_per_cell.png and b/docs/img/cycles_per_cell.png differ diff --git a/docs/img/demo.gif b/docs/img/demo.gif index a9e3e80..203a8d6 100644 Binary files a/docs/img/demo.gif and b/docs/img/demo.gif differ diff --git a/docs/img/manage_cells.png b/docs/img/manage_cells.png index 3cbfe3c..22aec40 100644 Binary files a/docs/img/manage_cells.png and b/docs/img/manage_cells.png differ diff --git a/docs/img/plot_types.png b/docs/img/plot_types.png index bf4cb10..2401b93 100644 Binary files a/docs/img/plot_types.png and b/docs/img/plot_types.png differ diff --git a/docs/img/projects_dark.png b/docs/img/projects_dark.png index 7196187..c8440be 100644 Binary files a/docs/img/projects_dark.png and b/docs/img/projects_dark.png differ diff --git a/docs/img/summary_collect.png b/docs/img/summary_collect.png index 34debbd..d4b2f76 100644 Binary files a/docs/img/summary_collect.png and b/docs/img/summary_collect.png differ diff --git a/docs/img/summary_grouped_light.png b/docs/img/summary_grouped_light.png index 53ccc43..8481d1c 100644 Binary files a/docs/img/summary_grouped_light.png and b/docs/img/summary_grouped_light.png differ diff --git a/llms-full.txt b/llms-full.txt index 237267c..48d57bd 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -61,7 +61,7 @@ FILE: README.md pywebview window (or your browser).

-## A tour in screenshots +## Some examples @@ -122,53 +122,21 @@ FILE: README.md ## Features -- **Zero-setup demo** — one click loads three bundled example cells (no files needed). -- **Load your own** `.cellpy` / legacy `.h5` files. -- **Import raw instrument files** — Arbin `.res`, Maccor (text), Neware, PEC and more are - processed into cellpy cells with a metadata step (mass / area / nominal capacity / cycle - mode). One-click bundled raw demos too. -- **Save & reopen projects** — explicit Save (not autosave) writes the loaded set plus - grouping / labels / selection into a portable project folder; reopen later. The project - tag shows when you have unsaved edits (`name*`), and **Close** clears the current - session after confirmation. Re-saving only rewrites cells whose *data* changed — - renaming or regrouping is roughly **10× faster** than a full write, because - those live in the manifest, not in the `.cellpy` files. -- **Cycle summary** across many cells — built with cellpy's own - `collect_summaries` + plotting, with a **plot-type selector** (capacity + CE, - capacity, coulombic efficiency, cumulated CE, end voltages, internal - resistance, C-rate, capacity loss), a gravimetric / areal / absolute basis, - optional group averaging with a mean ± std spread band, and independent or - shared y-scales. -- **Cell explorer** — cellpy's `collect_cycles` voltage–capacity curves for any - set of cycles (gravimetric / areal / absolute, method), with per-cell metric - tiles. Switch the same cycles to **dQ/dV** (incremental capacity) or - **dV/dQ** (differential voltage), charge / discharge / both. Tick **Compare - cells** to pick several cells, each with its own cycles (`3` here, `7, 12` - there), and draw them **overlaid** on one axis or side by side — same - controls, same exports. -- **Cycles collector** — the same three curve types across *every selected - cell*, laid out per cycle or per cell, or as a **film** (density) plot. -- **Load data lots of ways** through one **Add cells…** dialog: drop files, - **Browse…** (desktop) or **Upload…** (served), paste paths or **glob - patterns** (`*si*.h5`, capped at a configurable max), or **Find in a remote - folder…** — every source lands in a **staged list** you review (ready / - not found / check type) before one button loads it. Tabs for `.cellpy` / - `.h5` files, raw instrument files, and **native cellpy batch journals** - (`.json`); project folders open from the Project panel. Recent paths are - offered as suggestions. The Data panel folds away once cells are loaded. -- **Editable cell list** (the "journal"): rename, group, select/deselect, remove — - plus a **Manage cells** modal (filter/sort, select-by-group, remove all). -- **Instruments discovered from cellpy** at runtime (not hard-coded), with each - loader's sub-models. -- **Clear feedback**: a result card for loads (`Loaded 3 cells · 1 skipped ▸ - details`) and toast notifications for saves, opens, exports, and errors - (including corrupt journals). -- **Background loading** with live progress (SSE) — the UI never freezes. -- **Export** collected data to **CSV / Excel / Parquet / JSON**, and charts as - **PNG / SVG / PDF** from **Export ▾** (server-side via kaleido — install with - `uv sync --extra export`); the chart toolbar camera still saves a quick PNG. -- **Light & dark themes.** -- **Colorized terminal logging** via loguru (`CSG_LOG_LEVEL`, default `INFO`). +- **Demo cells** — three bundled examples, plus bundled raw-file demos. +- **`.cellpy` and legacy `.h5` files.** +- **Raw instrument files** — Arbin `.res`, Maccor (text), Neware, PEC, and others. Mass, area, nominal capacity, cycle mode. +- **Add cells** — drop, browse, upload, pasted paths, globs, or a remote folder. Staged list before load. Batch journals (`.json`). +- **Cycle summary** — capacity, CE, cumulated CE, end voltages, internal resistance, C-rate, capacity loss. Gravimetric, areal, or absolute. Group mean ± std. Shared or independent y-scales. +- **Cell explorer** — voltage–capacity, dQ/dV, dV/dQ. Compare cells, overlaid or side by side. +- **Cycles collector** — per cycle, per cell, or film plot. +- **Cell list** — rename, group, select, filter, sort, remove. +- **Projects** — save and reopen the loaded cells, grouping, labels, and selection. Unsaved edits show as `name*`. +- **Export** — CSV, Excel, Parquet, JSON; figures as PNG, SVG, PDF. +- **Light and dark themes.** +- **Background loading** with progress. +- **Instruments** from the installed cellpy, including each loader's sub-models. +- **Developer mode** — every summary family cellpy registers, raw traces, diagnostics (`run --dev`). +- **Logging** — `CSG_LOG_LEVEL`, default `INFO`. ## Install diff --git a/src/cellpy_simple_gui/core/collect.py b/src/cellpy_simple_gui/core/collect.py index daa7a80..1fb9f6a 100644 --- a/src/cellpy_simple_gui/core/collect.py +++ b/src/cellpy_simple_gui/core/collect.py @@ -37,22 +37,22 @@ "light": { "paper_bgcolor": "white", "plot_bgcolor": "white", - "font_color": "#1f2933", - "gridcolor": "#eceff3", - "linecolor": "#c7ccd4", - "tickcolor": "#c7ccd4", - "legend_bg": "rgba(255,255,255,0.6)", - "annotation": "#7b8794", + "font_color": "#1c1f22", + "gridcolor": "#e8eaec", + "linecolor": "#d5d8dc", + "tickcolor": "#d5d8dc", + "legend_bg": "rgba(255,255,255,0.75)", + "annotation": "#8b929a", }, "dark": { - "paper_bgcolor": "#1a1f26", - "plot_bgcolor": "#1a1f26", - "font_color": "#e6edf3", - "gridcolor": "#2d3640", - "linecolor": "#4a5560", - "tickcolor": "#4a5560", - "legend_bg": "rgba(26,31,38,0.75)", - "annotation": "#9aa5b1", + "paper_bgcolor": "#16181b", + "plot_bgcolor": "#16181b", + "font_color": "#e7e6e4", + "gridcolor": "#2c3136", + "linecolor": "#3e444a", + "tickcolor": "#3e444a", + "legend_bg": "rgba(22,24,27,0.8)", + "annotation": "#7c8186", }, } diff --git a/src/cellpy_simple_gui/desktop.py b/src/cellpy_simple_gui/desktop.py index 59639bd..fe7055a 100644 --- a/src/cellpy_simple_gui/desktop.py +++ b/src/cellpy_simple_gui/desktop.py @@ -95,7 +95,7 @@ def run_desktop() -> None: width=1360, height=900, min_size=(1024, 680), - background_color="#0f1420", + background_color="#141618", ) previous, _win32_keepalive = _install_ctrl_c_close(webview) icon = str(_WINDOW_ICON) if _WINDOW_ICON.is_file() else None diff --git a/src/cellpy_simple_gui/web/static/css/app.css b/src/cellpy_simple_gui/web/static/css/app.css index 0f40252..fabfa3e 100644 --- a/src/cellpy_simple_gui/web/static/css/app.css +++ b/src/cellpy_simple_gui/web/static/css/app.css @@ -2,43 +2,43 @@ cellpy simple gui — design system ============================================================ */ :root { - --accent: #4c8bf5; - --accent-2: #2fb6a3; + --accent: #1f8f6b; + --accent-2: #178066; --danger: #e4574f; - --radius: 12px; - --radius-sm: 8px; + --radius: 8px; + --radius-sm: 6px; --font: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif; - --shadow: 0 6px 24px rgba(8, 12, 24, 0.28); - --shadow-sm: 0 2px 8px rgba(8, 12, 24, 0.18); + --shadow: 0 1px 2px rgba(16, 18, 20, 0.18); + --shadow-sm: none; } -/* dark (default) */ +/* dark (default) — neutral, one teal accent (#177) */ :root[data-theme="dark"] { - --bg: #0f1420; - --bg-2: #151c2c; - --panel: #1a2233; - --panel-2: #202b40; - --line: #2a3550; - --text: #e8edf6; - --text-dim: #9aa7be; - --text-faint: #6b7793; - --chip: #212c42; - --input: #131a28; + --bg: #141618; + --bg-2: #1b1e21; + --panel: #1e2124; + --panel-2: #262a2e; + --line: #34383d; + --text: #e7e6e4; + --text-dim: #a4a8ad; + --text-faint: #7c8186; + --chip: #2a2e32; + --input: #121416; } /* light */ :root[data-theme="light"] { - --bg: #eef1f6; - --bg-2: #e6eaf1; + --bg: #f3f4f5; + --bg-2: #e8eaec; --panel: #ffffff; - --panel-2: #f4f6fa; - --line: #dfe4ec; - --text: #1b2333; - --text-dim: #5b6678; - --text-faint: #8b95a7; - --chip: #eef1f6; + --panel-2: #f7f8f8; + --line: #e1e3e5; + --text: #1c1f22; + --text-dim: #5c636b; + --text-faint: #8b929a; + --chip: #eef0f1; --input: #ffffff; - --shadow: 0 6px 20px rgba(40, 55, 90, 0.12); - --shadow-sm: 0 2px 8px rgba(40, 55, 90, 0.08); + --shadow: 0 1px 2px rgba(28, 31, 34, 0.08); + --shadow-sm: none; } * { box-sizing: border-box; } @@ -46,7 +46,7 @@ html, body { height: 100%; margin: 0; } body { font-family: var(--font); - background: radial-gradient(1200px 600px at 80% -10%, var(--bg-2), var(--bg)) fixed; + background: var(--bg); color: var(--text); font-size: 14px; -webkit-font-smoothing: antialiased; @@ -56,8 +56,7 @@ body { .topbar { height: 56px; display: flex; align-items: center; justify-content: space-between; padding: 0 18px; border-bottom: 1px solid var(--line); - /* Transparent so the body gradient shows through (#58). */ - background: transparent; + background: var(--panel); position: sticky; top: 0; z-index: 20; } .brand { display: flex; align-items: center; gap: 12px; } @@ -83,7 +82,7 @@ body { .layout { display: grid; grid-template-columns: 330px 1fr; height: calc(100vh - 56px); } .sidebar { border-right: 1px solid var(--line); padding: 16px; display: flex; flex-direction: column; - gap: 14px; overflow-y: auto; background: color-mix(in srgb, var(--panel) 40%, transparent); + gap: 14px; overflow-y: auto; background: var(--bg); min-height: 0; } /* min-width: 0 — allow the grid main column to shrink so a wide Plotly @@ -104,8 +103,8 @@ body { flex-direction: column; min-height: 220px; } -.panel-title { font-size: 12px; text-transform: uppercase; letter-spacing: 1.2px; - color: var(--text-faint); margin: 0 0 10px; font-weight: 700; } +.panel-title { font-size: 13px; text-transform: none; letter-spacing: 0; + color: var(--text); margin: 0 0 10px; font-weight: 600; } .panel-head { display: flex; align-items: center; justify-content: space-between; flex: none; } .panel-actions { display: flex; gap: 4px; } @@ -118,8 +117,8 @@ body { .btn:hover { border-color: var(--accent); } .btn:disabled { opacity: .45; cursor: not-allowed; } .btn-primary { - background: linear-gradient(135deg, var(--accent), var(--accent-2)); - border: none; color: #fff; box-shadow: var(--shadow-sm); + background: var(--accent); + border: none; color: #fff; box-shadow: none; } .btn-primary:hover { filter: brightness(1.06); } .btn-ghost { background: transparent; } @@ -300,7 +299,7 @@ input:focus, select:focus { border-color: var(--accent); } } .job-actions { display: flex; gap: 6px; flex: none; margin-left: auto; } .progress { height: 6px; background: var(--chip); border-radius: 999px; margin-top: 10px; overflow: hidden; } -.progress-bar { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-2)); transition: width .3s; } +.progress-bar { height: 100%; background: var(--accent); transition: width .3s; } .spinner { width: 15px; height: 15px; border-radius: 50%; border: 2px solid var(--line); border-top-color: var(--accent); animation: spin .7s linear infinite; flex: none; @@ -344,7 +343,7 @@ input:focus, select:focus { border-color: var(--accent); } .stat-lbl { font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 1px; } /* ---------- tabs ---------- */ -.tabs { display: flex; gap: 4px; margin-bottom: 16px; border-bottom: 1px solid var(--line); } +.tabs { display: flex; gap: 4px; margin-bottom: 8px; border-bottom: 1px solid var(--line); } .tabs button { background: transparent; border: none; color: var(--text-dim); padding: 10px 16px; font-weight: 600; font-size: 14px; cursor: pointer; border-bottom: 2px solid transparent; @@ -352,16 +351,16 @@ input:focus, select:focus { border-color: var(--accent); } .tabs button:hover { color: var(--text); } .tabs button.active { color: var(--text); border-bottom-color: var(--accent); } -.tabpane { display: flex; flex-direction: column; gap: 14px; flex: 1; min-height: 0; } +.tabpane { display: flex; flex-direction: column; gap: 10px; flex: 1; min-height: 0; } /* ---------- controls row ---------- */ .controls { - display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; + display: flex; align-items: flex-end; gap: 8px 10px; flex-wrap: wrap; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); - padding: 12px 14px; box-shadow: var(--shadow-sm); + padding: 8px 10px; box-shadow: var(--shadow-sm); } -.ctl { display: flex; flex-direction: column; gap: 5px; } -.ctl label { font-size: 11px; color: var(--text-faint); font-weight: 600; text-transform: uppercase; letter-spacing: .5px; } +.ctl { display: flex; flex-direction: column; gap: 2px; } +.ctl label { font-size: 11px; color: var(--text-faint); font-weight: 600; text-transform: none; letter-spacing: 0; } .ctl select, .ctl .num { min-width: 90px; } .num { width: 84px; } .yrange-inputs { display: flex; align-items: center; gap: 4px; } @@ -416,8 +415,8 @@ input:focus, select:focus { border-color: var(--accent); } max-height: calc(100vh - 56px - 24px); overflow-y: auto; } .plot-sidepane-title { - margin: 0; font-size: 11px; font-weight: 700; letter-spacing: 1.2px; - text-transform: uppercase; color: var(--text-faint); + margin: 0; font-size: 13px; font-weight: 600; letter-spacing: 0; + text-transform: none; color: var(--text); } .plot-sidepane .ctl { width: 100%; } .plot-sidepane .ctl select, .plot-sidepane .ctl .num { min-width: 0; width: 100%; }