From 07e0990ecf0f8b6e7164cc07e20d054638588caf Mon Sep 17 00:00:00 2001
From: npeter83
Date: Sun, 11 Oct 2026 02:59:51 +0200
Subject: [PATCH] =?UTF-8?q?design(files):=20the=20one-library=20round=20in?=
=?UTF-8?q?=20the=20lab=20=E2=80=94=20aligned=20list=20columns,=20the=20tr?=
=?UTF-8?q?ay,=20the=20done=20notice?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The Files page replaces the Downloads page: list rows with one column per value kind and a
right-click menu, an app-wide download tray, a done notice that copies the link. Picked
variants are logged in DESIGN.md.
---
docs/DESIGN.md | 4 +
frontend/src/design-lab/DesignLab.tsx | 11 +
frontend/src/design-lab/filesRound.tsx | 730 +++++++++++++++++++++++++
frontend/src/design-lab/labKit.tsx | 77 ++-
4 files changed, 819 insertions(+), 3 deletions(-)
create mode 100644 frontend/src/design-lab/filesRound.tsx
diff --git a/docs/DESIGN.md b/docs/DESIGN.md
index 53680f95..f3588f18 100644
--- a/docs/DESIGN.md
+++ b/docs/DESIGN.md
@@ -774,3 +774,7 @@ of a full-width panel and nothing on the right.
| 2026-10-10 | Clip mode in the players (E38) | Scissors in the control row + `C`, `[` `]`; the panel in the player with the watched video as preview; overview + zoomed strip on `ui/RangeTrack` (ends are sliders); a toast + the Ready notification after Cut | A ⋯ menu entry; permanent In / Out buttons; a big dialog with its own preview (a second Plex transcode / YouTube iframe) | Owner's picks A, P1, a; a film is hours and a clip seconds, so only a zoomed strip gives a hand the precision. The Downloads editor's cuts moved onto the same track (keyboard-steppable now) |
| 2026-10-10 | Crop in clip mode (E38) | A Crop toggle in the clip panel: a frame over the playing picture (`ui/CropFrame`, the editor's crop too), Free / 16:9 / 9:16 / 1:1, Whole picture; maximised YouTube keeps the panel in the bar's place; the wide side panel shuts while clipping | A frame always on the picture in clip mode (K2); a free shape only | Owner's K1 + a: a crop is rare, so it never covers the picture unasked; a phone or square clip needs a held shape. The iframe hides its picture's size: drawn on the 16:9 box, the cut fits it to the real picture |
| 2026-10-10 | Marks on the player chrome | `--media-mark` (amber) and `--media-danger` (light red), the same in both themes; `media-marks` points the focus ring (`--focus`) and the chosen mark (`--chosen`: a track's tick, a tab's underline; the label stays white) at the amber on the player and every chrome surface; `media-scope` (a form) also the errors at the red | The theme's accent ring, chosen text, warn and danger | On the chrome (#171717) a light theme's accent ring (Slate 2.84:1), warn (2.99:1) and danger (2.77:1) all fall under 3:1; the accent as chosen text under 4.5:1 |
+| 2026-10-11 | Files page (one library) | List rows by default, the media grid a toggle remembered per folder; a sidebar of automatic source folders, own folders, Shared with me and the storage meter; the paste-a-link box in the toolbar | A media grid by default | The library holds videos AND plain files: rows read both, show more per screen and scan by size and date |
+| 2026-10-11 | Library list columns | Every kind of value in its own column (name, source, phone-safe, retention, size, added, actions), one grid with a subgrid per row so they line up; an action that does not apply keeps its slot empty; a right-click menu on every row (the ⋯ opens the same menu) | Badges flowing after the title; icons that shift when one is missing | Owner: values of one kind must fall in one vertical line; a row's every action reachable by right click |
+| 2026-10-11 | Running downloads | An app-wide tray, bottom right, shown only while something runs or failed (reason + retry), foldable to a pill | A rail icon with a count and a popover | Downloading became an action you start anywhere; a running one must be visible without going to look |
+| 2026-10-11 | Download done | The notification says whether it plays on a phone and carries Link másolása + Megnyitás (the folder, the item focused); never an automatic jump | A plain "ready" notice; auto-navigation to the item | The daily path is paste → done → copy the link; a jump would pull the user out of what they were doing |
diff --git a/frontend/src/design-lab/DesignLab.tsx b/frontend/src/design-lab/DesignLab.tsx
index 33a3233b..bf846765 100644
--- a/frontend/src/design-lab/DesignLab.tsx
+++ b/frontend/src/design-lab/DesignLab.tsx
@@ -4,6 +4,7 @@ import { BatchA } from "./batchA";
import { BatchB } from "./batchB";
import { BatchC } from "./batchC";
import { BatchD } from "./batchD";
+import { FilesRound } from "./filesRound";
import { Chip } from "../components/ui/Chip";
import { Slider } from "../components/ui/NativeControls";
import { SURFACE } from "../components/ui/surfaces";
@@ -74,6 +75,9 @@ export default function DesignLab() {
/>
+
+
Open · Files, the one library
+
+ The page that replaces Downloads. The recommended variant has the accent edge.
+
+ );
+}
+
+// ── F1: the list and the grid ────────────────────────────────────────────────────────────────────
+
+// One grid for the whole list, each row a subgrid: every kind of value sits in its own column, so the
+// badges, the sizes and the action icons line up down the page. An action that does not apply keeps
+// its slot empty (a file has no "copy the play link").
+const LIST_COLUMNS =
+ "grid grid-cols-[4rem_minmax(0,1fr)_7rem_6.5rem_7.5rem_4.5rem_4.5rem_auto] items-center gap-x-3";
+const ROW = "col-span-full grid grid-cols-subgrid items-center rounded-lg px-2 py-1.5";
+
+function ListHead() {
+ const th = "text-2xs uppercase tracking-wider text-muted";
+ return (
+
+
+ Név
+ Forrás
+ Telefon
+ Megőrzés
+ Méret
+ Hozzáadva
+
+
+ );
+}
diff --git a/frontend/src/design-lab/labKit.tsx b/frontend/src/design-lab/labKit.tsx
index b5332f59..1bad5c48 100644
--- a/frontend/src/design-lab/labKit.tsx
+++ b/frontend/src/design-lab/labKit.tsx
@@ -1,10 +1,11 @@
import { useSyncExternalStore, type ReactNode } from "react";
import clsx from "clsx";
+import { Badge } from "../components/ui/Badge";
import { SURFACE } from "../components/ui/surfaces";
-// Building blocks of the design lab. Every decision is taken, so the lab is the reference half
-// only: each is the chosen rendering with the rule it became. A new design round brings
-// back the side-by-side / pair (as before) for its open questions. Everything
+// Building blocks of the design lab. A taken decision is a : the chosen rendering with the
+// rule it became. An open design round shows its questions as s with their s side
+// by side, until the owner picks. Everything
// is static markup on purpose: a rendering that only exists here must not need app state.
/** A decision already taken: the chosen rendering only, with the rule it became. The lab's
@@ -36,6 +37,76 @@ export function Reference({
);
}
+/** An open question of a design round: what the app does today, then the variants side by side. */
+export function Decision({
+ id,
+ code,
+ title,
+ question,
+ now,
+ children,
+}: {
+ id: string;
+ code: string;
+ title: string;
+ question: string;
+ /** What the app does today. */
+ now: ReactNode;
+ children: ReactNode;
+}) {
+ return (
+
+
+
+ {code}
+
{title}
+
+
{question}
+
+ Today:
+ {now}
+
+
+
{children}
+
+ );
+}
+
+/** One answer to a Decision; the recommended one carries the accent edge. */
+export function Variant({
+ letter,
+ name,
+ recommended = false,
+ why,
+ children,
+}: {
+ letter: string;
+ name: string;
+ recommended?: boolean;
+ why: string;
+ children: ReactNode;
+}) {
+ return (
+
+
+
+ {letter}
+
+
{name}
+ {recommended && Recommended}
+
+
{children}
+
{why}
+
+ );
+}
+
/** A small caption above a sample inside a variant. */
export function Sample({ label, children }: { label: string; children: ReactNode }) {
return (