Add Web Push, guided start screen, SVG icons, chunked uploads, rename

- Web Push (VAPID + RFC 8291, pure Rust): permission/question/finished
  notifications while no app window is visible; settings with test button
- Guided new-session flow: task, craft target (website/app/game/old
  software/fresh), clean room or deep, description, model, folder
  (new, existing or cloned git repository); originals upload to original/
- Craft phase bar (craft_phase tool) for study/specify/build/check/ready
- Chunked uploads (8 MB) up to server.max_upload_mb (default 500 MB)
- All UI glyphs and emojis replaced by themeable SVG icons
- Rename dialog with manual name or model-generated suggestion

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-08 21:03:26 +01:00
1 parent 013695722a
commit a0d0e707f2
48 files changed
+2724 -228

No files matched your search

+229
View File
@@ -0,0 +1,229 @@
import type { ReactNode } from "react";
/**
* Line icons drawn with `currentColor`: they take the text colour of their
* context, so every theme colours them (no per-theme icon sets needed).
*/
const P: Record<string, ReactNode> = {
menu: <path d="M4 6h16M4 12h16M4 18h16" />,
close: <path d="M6 6l12 12M18 6 6 18" />,
check: <path d="m5 12.5 4.5 4.5L19 7" />,
"x-mark": <path d="M7 7l10 10M17 7 7 17" />,
"arrow-up": <path d="M12 19V5M6 11l6-6 6 6" />,
"arrow-down": <path d="M12 5v14M6 13l6 6 6-6" />,
"arrow-left": <path d="M19 12H5M11 6l-6 6 6 6" />,
"chevron-right": <path d="m9 6 6 6-6 6" />,
"chevron-down": <path d="m6 9 6 6 6-6" />,
share: (
<>
<circle cx="18" cy="5.5" r="2.5" />
<circle cx="6" cy="12" r="2.5" />
<circle cx="18" cy="18.5" r="2.5" />
<path d="m8.2 10.8 7.6-4M8.2 13.2l7.6 4" />
</>
),
fork: (
<>
<circle cx="6" cy="5" r="2" />
<circle cx="18" cy="5" r="2" />
<circle cx="12" cy="19" r="2" />
<path d="M6 7v2a3 3 0 0 0 3 3h6a3 3 0 0 0 3-3V7M12 12v5" />
</>
),
download: <path d="M12 4v11M7 10l5 5 5-5M5 20h14" />,
compact: <path d="M8 4l4 4 4-4M8 20l4-4 4 4M4 12h16" />,
trash: <path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13M10 11v6M14 11v6" />,
edit: <path d="M4 20h4L19 9l-4-4L4 16v4ZM13.5 6.5l4 4" />,
undo: <path d="M9 14 4 9l5-5M4 9h10a6 6 0 0 1 0 12h-3" />,
clip: <path d="m20 11-8.5 8.5a5 5 0 0 1-7-7L13 4a3.5 3.5 0 0 1 5 5l-8.5 8.5a2 2 0 0 1-3-3L14 7" />,
file: (
<>
<path d="M6 3h8l4 4v14H6Z" />
<path d="M14 3v4h4" />
</>
),
"file-text": (
<>
<path d="M6 3h8l4 4v14H6Z" />
<path d="M14 3v4h4M9 12h6M9 16h6" />
</>
),
"file-doc": (
<>
<path d="M6 3h8l4 4v14H6Z" />
<path d="M14 3v4h4M9 11h6M9 14h6M9 17h4" />
</>
),
folder: <path d="M3 6a1 1 0 0 1 1-1h5l2 2h9a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1Z" />,
"folder-up": (
<>
<path d="M3 6a1 1 0 0 1 1-1h5l2 2h9a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1Z" />
<path d="M12 16v-5M9.5 13.5 12 11l2.5 2.5" />
</>
),
folders: (
<>
<path d="M7 8a1 1 0 0 1 1-1h4l1.5 1.5H20a1 1 0 0 1 1 1V18a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1Z" />
<path d="M4 15V6a1 1 0 0 1 1-1h4" />
</>
),
image: (
<>
<rect x="3" y="5" width="18" height="14" rx="2" />
<circle cx="9" cy="10" r="1.8" />
<path d="m4 17 5-4 4 3 3-2 4 3" />
</>
),
music: (
<>
<path d="M9 18V6l11-2v12" />
<circle cx="6.5" cy="18" r="2.5" />
<circle cx="17.5" cy="16" r="2.5" />
</>
),
cube: (
<>
<path d="m12 3 8 4.5v9L12 21l-8-4.5v-9Z" />
<path d="m4 7.5 8 4.5 8-4.5M12 12v9" />
</>
),
external: <path d="M14 4h6v6M20 4l-9 9M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5" />,
refresh: <path d="M20 11a8 8 0 1 0-2.3 5.7M20 4v7h-7" />,
lock: (
<>
<rect x="5" y="11" width="14" height="9" rx="2" />
<path d="M8 11V8a4 4 0 0 1 8 0v3M12 15v2" />
</>
),
question: (
<>
<circle cx="12" cy="12" r="9" />
<path d="M9.5 9.5a2.5 2.5 0 1 1 3.5 2.3c-.6.3-1 .8-1 1.5V14M12 17h.01" />
</>
),
"radio-on": (
<>
<circle cx="12" cy="12" r="8" />
<circle cx="12" cy="12" r="3.5" className="fill" />
</>
),
"radio-off": <circle cx="12" cy="12" r="8" />,
"box-on": (
<>
<rect x="4" y="4" width="16" height="16" rx="3" />
<path d="m8 12 3 3 5-6" />
</>
),
"box-off": <rect x="4" y="4" width="16" height="16" rx="3" />,
play: <path d="M8 5v14l11-7Z" className="fill" />,
stop: <rect x="6" y="6" width="12" height="12" rx="2" className="fill" />,
warning: <path d="M12 4 2.5 20h19ZM12 10v4.5M12 17.5h.01" />,
brain: (
<>
<path d="M9 4a3 3 0 0 0-3 3 3 3 0 0 0-2 5 3 3 0 0 0 2 5 3 3 0 0 0 6 1V5a2 2 0 0 0-3-1Z" />
<path d="M15 4a3 3 0 0 1 3 3 3 3 0 0 1 2 5 3 3 0 0 1-2 5 3 3 0 0 1-6 1" />
</>
),
eye: (
<>
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z" />
<circle cx="12" cy="12" r="3" />
</>
),
gear: (
<>
<circle cx="12" cy="12" r="3" />
<path d="M12 2.5v3M12 18.5v3M21.5 12h-3M5.5 12h-3M18.7 5.3l-2.1 2.1M7.4 16.6l-2.1 2.1M18.7 18.7l-2.1-2.1M7.4 7.4 5.3 5.3" />
</>
),
robot: (
<>
<rect x="5" y="8" width="14" height="11" rx="3" />
<path d="M12 4v4M9 13h.01M15 13h.01M9.5 16h5M3 12v3M21 12v3" />
</>
),
plug: <path d="M9 3v5M15 3v5M6 8h12v3a6 6 0 0 1-12 0ZM12 17v4" />,
shield: (
<>
<path d="M12 3 5 6v6c0 4.2 3 7.6 7 9 4-1.4 7-4.8 7-9V6Z" />
<path d="m9 12 2 2 4-4" />
</>
),
users: (
<>
<circle cx="9" cy="8" r="3.2" />
<path d="M3 20a6 6 0 0 1 12 0M16 4.5a3.2 3.2 0 0 1 0 6.3M18 14.5a6 6 0 0 1 3 5.5" />
</>
),
user: (
<>
<circle cx="12" cy="8" r="3.8" />
<path d="M4.5 20.5a7.5 7.5 0 0 1 15 0" />
</>
),
toolbox: (
<>
<rect x="3" y="8" width="18" height="11" rx="2" />
<path d="M9 8V5h6v3M3 13h18M10 12v2M14 12v2" />
</>
),
scroll: <path d="M7 4h11a2 2 0 0 1 0 4h-1v10a2 2 0 0 1-2 2H6a2 2 0 0 1 0-4h1ZM10 9h4M10 12h4" />,
terminal: (
<>
<rect x="3" y="4" width="18" height="16" rx="2" />
<path d="m7 9 3 3-3 3M12 15h5" />
</>
),
logout: <path d="M10 4H5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h5M15 8l4 4-4 4M19 12H9" />,
chart: <path d="M4 20V4M4 20h16M8 16v-4M12 16V8M16 16v-6M20 16V6" />,
wrench: <path d="M14.5 5.5a4 4 0 0 0 5 5L11 19a2.1 2.1 0 0 1-3-3l8.5-8.5a4 4 0 0 1-2-2Z" />,
sun: (
<>
<circle cx="12" cy="12" r="4" />
<path d="M12 2.5v2M12 19.5v2M21.5 12h-2M4.5 12h-2M18.7 5.3l-1.4 1.4M6.7 17.3l-1.4 1.4M18.7 18.7l-1.4-1.4M6.7 6.7 5.3 5.3" />
</>
),
moon: <path d="M20 14.5A8 8 0 1 1 9.5 4a6.5 6.5 0 0 0 10.5 10.5Z" />,
"theme-auto": (
<>
<circle cx="12" cy="12" r="8.5" />
<path d="M12 3.5v17a8.5 8.5 0 0 0 0-17Z" className="fill" />
</>
),
dot: <circle cx="12" cy="12" r="4.5" className="fill" />,
plus: <path d="M12 5v14M5 12h14" />,
minus: <path d="M5 12h14" />,
upload: <path d="M12 16V4M7 9l5-5 5 5M4 16v3a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-3" />,
more: (
<>
<circle cx="5" cy="12" r="1.6" className="fill" />
<circle cx="12" cy="12" r="1.6" className="fill" />
<circle cx="19" cy="12" r="1.6" className="fill" />
</>
),
copy: (
<>
<rect x="8" y="8" width="12" height="12" rx="2" />
<path d="M16 8V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3" />
</>
),
sparkle: <path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8Z" />,
};
export type IconName = keyof typeof P;
export function Icon({ name, size, className, title }: { name: string; size?: number; className?: string; title?: string }) {
return (
<svg
className={`icon ${className ?? ""}`}
viewBox="0 0 24 24"
width={size}
height={size}
aria-hidden={title ? undefined : true}
role={title ? "img" : undefined}
>
{title && <title>{title}</title>}
{P[name] ?? P.dot}
</svg>
);
}