Fix stale PWA service worker after updates; collapsible task list; short screens

Installed apps checked their service worker at /sw-<old build>.js. That file no
longer exists after an update and the static fallback answered with index.html,
so the update check failed and the old worker kept serving the previous app
shell. The old frontend crashed on the new image references (base64Size of a
missing `data`), leaving a blank page on phones. Old service worker URLs now get
the clean-up worker (/sw.js), which unregisters and reloads; missing scripts and
styles answer 404 instead of HTML.

- Task list above the composer folds to one line (progress, current task),
  remembered per device.
- max-height: 560px layout (square phones such as the Clicks Communicator,
  landscape): tighter top bar, composer and new-chat wizard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-09 22:15:45 +01:00
1 parent b1fa54a7b6
commit d7f89ee257
6 files changed
+232 -8

No files matched your search

+24 -6
View File
@@ -7,9 +7,9 @@ import { useModels } from "../hooks";
import { fmtCompact, fmtCost, fmtNumber, fmtPercent, useT, type Key } from "../i18n";
import { navigate } from "../router";
import { dismissError, loadSession, loadSessions, useStore } from "../store";
import type { Aggregate, ImageData, Message, Part, Session } from "../types";
import type { Aggregate, ImageData, Message, Part, Session, Todo } from "../types";
import { ActivityLine, Dots, Spinner, ThinkIcon, WorkBar } from "./Activity";
import { useReasoningMode } from "../prefs";
import { useReasoningMode, useTodosCollapsed } from "../prefs";
import { Composer } from "./Composer";
import { ShareDialog } from "./ShareDialog";
import { RenameDialog } from "./RenameDialog";
@@ -503,10 +503,7 @@ export function ChatView({ sessionId, onMenu, onClosePane }: { sessionId: string
<ActivityLine sessionId={sessionId} draft={draft} pendingTool={pendingTool} waiting={waiting} permissionPending={permissions.length > 0 || questions.length > 0} />
)}
{openTodos.length > 0 && (busy || permissions.length > 0 || questions.length > 0) && (
<div className="todos">
<div style={{ color: "var(--muted)", fontSize: 12 }}>{t("tool.todos")}</div>
<TodoList todos={todos} />
</div>
<TodoPanel todos={todos} />
)}
{permissions.map((p) => (
<PermissionCard key={p.id} req={p} currentId={sessionId} />
@@ -633,3 +630,24 @@ function PhaseBar({ phase, deep }: { phase?: string; deep: boolean }) {
</div>
);
}
/** Tasks of the model above the composer; folds to one line (current task, progress). */
function TodoPanel({ todos }: { todos: Todo[] }) {
const t = useT();
const [collapsed, setCollapsed] = useTodosCollapsed();
const done = todos.filter((x) => x.status === "completed").length;
const current = todos.find((x) => x.status === "in_progress");
return (
<div className={`todos ${collapsed ? "collapsed" : ""}`}>
<button className="todos-head" onClick={() => setCollapsed(!collapsed)} aria-expanded={!collapsed}>
<Icon name={collapsed ? "chevron-right" : "chevron-down"} />
<span>{t("tool.todos")}</span>
<span className="todos-count">
{done}/{todos.length}
</span>
{collapsed && current && <span className="todos-current">{current.content}</span>}
</button>
{!collapsed && <TodoList todos={todos} />}
</div>
);
}
+18
View File
@@ -25,3 +25,21 @@ export function useReasoningMode(): ReasoningMode {
getReasoningMode,
);
}
const TODOS_KEY = "codecraft.todosCollapsed";
/** Task list above the composer folded to a one-line summary (per device). */
export function useTodosCollapsed(): [boolean, (v: boolean) => void] {
const v = useSyncExternalStore(
(l) => {
listeners.add(l);
return () => listeners.delete(l);
},
() => localStorage.getItem(TODOS_KEY) === "1",
);
const set = (c: boolean) => {
localStorage.setItem(TODOS_KEY, c ? "1" : "0");
listeners.forEach((l) => l());
};
return [v, set];
}
+134
View File
@@ -721,6 +721,39 @@ button.session-group:hover {
margin: 8px 0;
font-size: 13px;
}
.todos-head {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
min-width: 0;
padding: 0;
background: none;
border: none;
color: var(--muted);
font: inherit;
font-size: 12px;
text-align: left;
cursor: pointer;
}
.todos-head:hover {
color: var(--fg1);
}
.todos-count {
font-variant-numeric: tabular-nums;
}
.todos-current {
flex: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--accent);
font-weight: 600;
}
.todos.collapsed {
padding-block: 6px;
}
.todos ul {
list-style: none;
padding: 0;
@@ -3773,3 +3806,104 @@ span.chat-tab > button {
.instructions-modal {
width: min(640px, calc(100vw - 32px));
}
/* --- short screens ----------------------------------------------------------
* Square phones (e.g. Clicks Communicator, ~393x400 CSS px with a hardware
* keyboard) and phones in landscape: height is the scarce dimension, so bars,
* the composer and the new-chat wizard get tighter and the chat keeps the room. */
@media (max-height: 560px) {
.topbar {
min-height: 40px;
padding: 4px 10px;
padding-top: max(4px, env(safe-area-inset-top));
gap: 4px;
}
.topbar button.ghost {
padding: 4px 6px;
}
.chat {
padding: 10px 12px 14px;
}
.msg {
margin-bottom: 10px;
}
.composer-wrap {
padding: 6px 10px;
padding-bottom: max(6px, env(safe-area-inset-bottom));
}
.composer textarea {
min-height: 38px;
max-height: 30vh;
padding: 8px 10px;
}
/* One placeholder line instead of two (the textarea grows with its content). */
.composer textarea::placeholder {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.composer-bar {
margin-top: 4px;
gap: 4px;
}
.composer-bar select {
max-width: 150px;
padding: 2px 4px;
}
.composer-actions button {
padding: 4px 10px;
}
.todos {
margin: 4px 0;
padding: 6px 10px;
}
.page {
padding: 12px;
}
.wiz-title {
font-size: 19px;
margin: 0 0 8px;
}
.wiz-sub {
display: none;
}
.wiz-panel {
padding: 12px;
}
.wiz-step {
margin-bottom: 16px;
}
.wiz-step h3 {
font-size: 16px;
margin-bottom: 8px;
}
.wiz-cards,
.wiz-cards.five {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 8px;
}
.wiz-card {
padding: 8px 10px;
gap: 8px;
min-height: 44px;
font-size: 14px;
}
.wiz-card.active {
padding: 7px 9px;
}
.wiz-card-text {
padding-right: 14px;
}
.wiz-card-desc {
display: none;
}
.wiz-icon {
width: 20px;
height: 20px;
}
.wiz-num {
width: 24px;
height: 24px;
font-size: 12px;
}
}
+4
View File
@@ -3,6 +3,10 @@
* under "unreleased"; scripts/release.sh renames the key to the release version.
*/
export const WHATS_NEW: Record<string, { en: string[]; de: string[] }> = {
unreleased: {
en: ["The task list of the model can be folded (click on \"Tasks\")", "Better layout on square and short screens (e.g. Clicks Communicator, landscape)"],
de: ["Die Aufgabenliste des Modells lässt sich zuklappen (Klick auf „Aufgaben“)", "Besseres Layout auf quadratischen und niedrigen Displays (z. B. Clicks Communicator, Querformat)"],
},
"202611-00010": {
en: ["Chats with many images (screenshots, generated images) open much faster – images load when they scroll into view"],
de: ["Chats mit vielen Bildern (Screenshots, generierte Bilder) öffnen viel schneller – Bilder laden erst beim Hinscrollen"],