[eric] canvas: app card chrome carries no identity (bare strip); the dock tile is the personalized mark and prefers the app's real thumbnail over any glyph

This commit is contained in:
ciregenz
2026-08-09 12:13:05 -07:00
parent d013ac5f1f
commit c8c3e7be0e
2 changed files with 16 additions and 39 deletions
@@ -645,16 +645,6 @@ const DashboardViewCard: React.FC<Props> = ({
const dockActive = !!dockRect && !dragging && !localResize && !isTiled && !isMinimized;
const tiledSize = useTiledCard({ cardId: cardKey, zone: tileZone, active: !isMinimized, originX: displayX, originY: displayY, getCamera: getCanvasState });
// Same identity math as the dock and Applications window, shrunk to chrome size.
const appHue = ((): number => {
let h = 0;
const name = output.name || '?';
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
return h % 360;
})();
const p_glyph = (output.icon || '').trim();
const appGlyph = p_glyph && p_glyph.length <= 3 ? p_glyph : (output.name || '?').trim().charAt(0).toUpperCase();
// The old 8-icon header cluster, demoted behind one kebab: view switcher, new window, toolbar
// collapse, then the same rows the card's right-click menu carries.
const headerKebabRows = (): CardMenuRow[] => ([
@@ -817,29 +807,6 @@ const DashboardViewCard: React.FC<Props> = ({
<Box onPointerDown={(e) => e.stopPropagation()} sx={{ display: 'flex', alignItems: 'center', flexShrink: 0, mr: 0.25 }}>
<WindowControls onClose={() => handleRemove()} onMinimize={onMinimize} onTile={onTile} tiled={!!tileZone} />
</Box>
{/* Centered identity: the app's own monogram tile, no name (bare-chrome grammar shared with Settings/Marketplace; the tile matches the dock and Applications window). */}
<Box sx={{ position: 'absolute', left: '50%', transform: 'translateX(-50%)', display: 'flex', alignItems: 'center', gap: 0.5, pointerEvents: 'none' }} title={output.name}>
<Box
sx={{
width: 17,
height: 17,
borderRadius: '5px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.5625rem',
fontWeight: 700,
color: 'rgba(255,255,255,0.94)',
background: `linear-gradient(160deg, hsl(${appHue}, 42%, 52%), hsl(${(appHue + 24) % 360}, 48%, 34%))`,
boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.14)',
}}
>
{appGlyph}
</Box>
{instance > 1 && (
<Typography sx={{ fontSize: '0.6875rem', fontWeight: 600, color: c.text.ghost }}>#{instance}</Typography>
)}
</Box>
<Box sx={{ flex: 1 }} />
{showControls && (
@@ -2,11 +2,21 @@ import React, { useState } from 'react';
import Box from '@mui/material/Box';
import type { DockEntry } from './dockEntries';
/** A tile shows its site favicon OR the generic glyph, never the glyph peeking out from behind the favicon. */
/** A tile shows its real image (site favicon, app thumbnail) OR the generic SVG glyph, never
* letters and never the glyph peeking out from behind an image. */
export function DockTileIcon({ entry }: { entry: DockEntry }): React.ReactElement {
const [faviconFailed, setFaviconFailed] = useState(false);
if (!entry.faviconUrl || faviconFailed) return <>{entry.icon}</>;
return (
<Box component="img" src={entry.faviconUrl} alt="" onError={() => setFaviconFailed(true)} sx={{ borderRadius: '6px' }} />
);
const [imageFailed, setImageFailed] = useState(false);
if (entry.faviconUrl && !imageFailed) {
return (
<Box component="img" src={entry.faviconUrl} alt="" onError={() => setImageFailed(true)} sx={{ borderRadius: '6px' }} />
);
}
// An app's own screenshot fills the whole tile: the most personalized mark it can carry.
if (entry.kind === 'view' && entry.thumbnail && !imageFailed) {
return (
<Box component="img" src={entry.thumbnail} alt="" onError={() => setImageFailed(true)}
sx={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }} />
);
}
return <>{entry.icon}</>;
}