UI: text wraps inside its box everywhere (global wrapping, shrinkable flex items, wrapping buttons, unknown icon fallback)

This commit is contained in:
Claude committed 2026-10-01 01:37:42 +00:00
1 parent 6c58042a32
commit 6fd78f32ea
8 files changed
+54 -9

No files matched your search

+14 -1
View File
@@ -40,10 +40,17 @@ html, body {
-webkit-font-smoothing: antialiased;
user-select: none;
-webkit-user-select: none;
/* Long names and unbroken strings wrap inside their box instead of spilling out of it. */
overflow-wrap: anywhere;
}
#app { height: 100%; }
/* Flex and grid items may shrink below their content, so long names wrap or truncate inside their box instead of pushing it wider. */
*, *::before, *::after { min-width: 0; }
svg, img, canvas, video, input[type='checkbox'], input[type='radio'] { flex-shrink: 0; }
h1, h2, h3, h4 { margin: 0; letter-spacing: -0.015em; }
p { margin: 0; }
a { color: var(--accent); text-decoration: none; }
@@ -85,8 +92,11 @@ button {
font-weight: 520;
font-size: 0.9rem;
transition: background 0.15s, border-color 0.15s, opacity 0.15s;
white-space: nowrap;
/* Labels may wrap when space runs out; compact buttons below stay on one line. */
white-space: normal;
max-width: 100%;
}
button.primary, button.ghost, button.danger, button.icon, button.tool { white-space: nowrap; }
button:hover { background: color-mix(in srgb, var(--text) 11%, transparent); }
button:active { background: color-mix(in srgb, var(--text) 14%, transparent); }
button:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
@@ -154,3 +164,6 @@ label.field { display: flex; flex-direction: column; gap: 0.45rem; font-size: 0.
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes fade-up { from { opacity: 0; transform: translateY(0.6rem); } to { opacity: 1; transform: none; } }
@keyframes fade { from { opacity: 0; } }
code, pre, kbd, samp { overflow-wrap: anywhere; word-break: break-word; }
pre { white-space: pre-wrap; }
+1 -1
View File
@@ -15,7 +15,7 @@
{/if}
<style>
.plink { all: unset; cursor: pointer; font: inherit; color: inherit; border-radius: 4px; transition: color 0.12s ease; }
.plink { all: unset; cursor: pointer; font: inherit; color: inherit; border-radius: 4px; transition: color 0.12s ease; display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: inherit; vertical-align: bottom; }
.plink:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
.plink:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
</style>
+1 -1
View File
@@ -86,7 +86,7 @@
.inst.active { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--text); border-color: color-mix(in srgb, var(--accent) 55%, transparent); box-shadow: 0 0 16px color-mix(in srgb, var(--accent) 30%, transparent); }
.inst.active::before { content: ''; position: absolute; left: -0.75rem; top: 0.65rem; bottom: 0.65rem; width: 3.5px; border-radius: 0 3px 3px 0; background: var(--accent); box-shadow: 0 0 8px var(--accent); }
.inst.running::after { content: ''; position: absolute; right: -0.2rem; bottom: -0.2rem; width: 0.8rem; height: 0.8rem; border-radius: 50%; background: var(--success); border: 2px solid var(--bg); }
.tip { position: fixed; left: 5.2rem; transform: translateY(0); font-style: normal; font-size: 0.82rem; font-weight: 600; white-space: nowrap; padding: 0.4rem 0.75rem; border-radius: 0.5rem; background: var(--surface); border: 1px solid var(--line-strong); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); opacity: 0; pointer-events: none; transition: opacity 0.15s; z-index: 50; color: var(--text); }
.tip { position: fixed; left: 5.2rem; transform: translateY(0); font-style: normal; font-size: 0.82rem; font-weight: 600; max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0.4rem 0.75rem; border-radius: 0.5rem; background: var(--surface); border: 1px solid var(--line-strong); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); opacity: 0; pointer-events: none; transition: opacity 0.15s; z-index: 50; color: var(--text); }
.inst:hover .tip { opacity: 1; }
.bottom { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; }
.big { width: 2.6rem; height: 2.6rem; position: relative; border-radius: var(--radius-sm); transition: transform 0.12s; }
+1 -1
View File
@@ -67,7 +67,7 @@
.ph.on { background: color-mix(in srgb, var(--accent) 30%, transparent); }
.info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.05rem; }
.name { gap: 0.5rem; }
.name strong { font-size: 0.85rem; font-weight: 560; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.name strong { min-width: 0; flex: 0 1 auto; font-size: 0.85rem; font-weight: 560; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.state { font-size: 0.72rem; font-weight: 520; color: var(--muted); display: inline-flex; align-items: center; gap: 0.3rem; }
.state::before { content: ''; width: 0.45rem; height: 0.45rem; border-radius: 50%; background: currentColor; }
.state.online { color: var(--success); }
+15
View File
@@ -133,7 +133,22 @@ const wallet = {
};
const mockWallet = () => JSON.parse(JSON.stringify(wallet));
// `?stress` in the dev URL makes every name, title and description much longer, to check that no screen lets text spill out of its box.
const STRESS = typeof location !== 'undefined' && new URLSearchParams(location.search).has('stress');
const TEXT_KEYS = /(^|_)(name|title|username|description|motd|message|desc|label|bio|tagline|text|rank|guild)(_|$)/i;
function stress(value: unknown, key = ''): unknown {
if (Array.isArray(value)) return value.map((v) => stress(v, key));
if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, stress(v, k)]));
if (typeof value === 'string' && TEXT_KEYS.test(key) && /^[A-Za-z][A-Za-z '&:,.\-]{2,60}$/.test(value)) return `${value} With An Extraordinarily Long Unbroken Continuation`;
return value;
}
export async function mockInvoke(cmd: string, args: Record<string, any>): Promise<unknown> {
const result = await mockInvokeInner(cmd, args);
return STRESS ? stress(result) : result;
}
async function mockInvokeInner(cmd: string, args: Record<string, any>): Promise<unknown> {
await sleep(40);
switch (cmd) {
case 'get_public_servers':