Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ jobs:
- uses: ./.github/actions/setup
- name: Tauri system libraries
run: |
# Azure package downloads can stall until the whole job expires:
# https://github.com/actions/runner-images/issues/14594
# Keep the image's official HTTPS archive and security fallbacks.
sudo sed -i '\|^http://azure\.archive\.ubuntu\.com/ubuntu/|d' /etc/apt/apt-mirrors.txt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 This looks right, but it is unrelated to attachments and affects every branch's native job. A separate small PR would let other PRs get the fix now and give it its own revert point when actions/runner-images#14594 is closed.

sudo apt-get update
sudo apt-get install --no-install-recommends -y dbus-daemon libwebkit2gtk-4.1-dev build-essential libssl-dev librsvg2-dev libayatana-appindicator3-dev patchelf
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
Expand Down
16 changes: 3 additions & 13 deletions src/bundled/voice-notes/VoiceNoteCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useMediaCorners } from "../../features/messages/use-media-corners";
import { useMediaElementSource } from "../../features/messages/use-media-element-source";
import { Button } from "../../shared/design-system/ui/Button";
import { IconButton } from "../../shared/design-system/ui/IconButton";
Expand Down Expand Up @@ -52,8 +51,6 @@ function Player({
source: string | undefined;
unavailable: boolean;
}) {
const corners = useMediaCorners();
const playCorners = useMediaCorners();
const audio = useRef<HTMLAudioElement>(null);
const active = useConversationPresentation();
useEffect(() => {
Expand Down Expand Up @@ -157,19 +154,12 @@ function Player({
}
}
return (
<fieldset ref={corners} className={styles.card}>
<fieldset className={styles.card}>
<legend className="sr-only">Voice note</legend>
<svg
className={styles.cardSurface}
data-image-outline=""
aria-hidden="true"
>
<path />
</svg>
<span ref={playCorners} className={styles.play}>
<span className={styles.play}>
<IconButton
type="button"
variant="solid"
variant="bare"
disabled={!source}
size="compact"
style={{ width: "100%", height: "100%", borderRadius: "inherit" }}
Expand Down
68 changes: 14 additions & 54 deletions src/bundled/voice-notes/VoiceNotes.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,60 +58,40 @@
display: flex;
align-items: center;
box-sizing: border-box;
gap: calc(var(--space-5) / 2);
gap: calc(var(--space-5) / 2 + var(--space-1));
width: 21rem;
min-width: 0;
max-width: 100%;
padding: var(--space-2) calc(var(--space-5) / 2);
margin: var(--space-1) 0;
border: 1px solid var(--voice-card-border);
border-radius: var(--radius-container);
border-radius: var(--radius-icon-button);
background: var(--voice-card-background);
transition:
background-color 150ms var(--easing-state),
border-color 150ms var(--easing-state);
}
.cardSurface {
display: none;
}
.card[data-smooth-corners] {
isolation: isolate;
background: transparent;
border-color: transparent;
}
.card[data-smooth-corners] .cardSurface {
display: block;
position: absolute;
z-index: -1;
inset: -1px;
width: calc(100% + 2px);
height: calc(100% + 2px);
pointer-events: none;
clip-path: var(--media-corner-clip);
}
.cardSurface > path {
fill: var(--voice-card-background);
stroke: var(--voice-card-border);
stroke-width: 2px;
transition:
fill 150ms var(--easing-state),
stroke 150ms var(--easing-state);
}
.play {
display: inline-flex;
border-radius: var(--radius-icon-button);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 This border-radius does nothing: the second .play rule at line 94 overrides it. Merging the two .play blocks would make the real radius clear.

}
.play[data-smooth-corners] > button {
clip-path: var(--media-corner-clip);
background: color-mix(
in srgb,
var(--voice-card-background) 90%,
var(--text-standard)
);
}
.play:has(:focus-visible) {
outline: 2px solid var(--border-focus);
outline-offset: 2px;
}
.play {
flex-shrink: 0;
width: 36px;
height: 36px;
width: calc(36px + 2 * var(--space-1h));
height: calc(36px + 2 * var(--space-1h));
margin-block: calc(-1 * var(--space-1h));
margin-inline-start: calc(-1 * var(--space-2));
margin-inline-end: calc(-1 * var(--space-1));
border-radius: calc(var(--radius-icon-button) - var(--space-half) - 1px);
}
.content {
flex: 1;
Expand Down Expand Up @@ -279,33 +259,13 @@
animation: none;
}
.card,
.cardSurface > path,
.details > *,
.playIcon > svg {
transition: none;
}
}

/* Composer drafts share the enclosing composer's circular inset geometry. */
/* Draft and sent voice notes share the same card and inset play geometry. */
[data-voice-draft] .card {
margin: 0;
border-radius: var(--radius-icon-button);
background: var(--voice-card-background);
border-color: var(--voice-card-border);
}
[data-voice-draft] .card .cardSurface {
display: none;
}

[data-voice-draft] .play {
/* Expand into the chip padding without moving the text/waveform column. */
width: calc(36px + 2 * var(--space-1h));
height: calc(36px + 2 * var(--space-1h));
margin-block: calc(-1 * var(--space-1h));
margin-inline-start: calc(-1 * var(--space-2));
margin-inline-end: calc(-1 * var(--space-1));
border-radius: calc(var(--radius-icon-button) - var(--space-half) - 1px);
}
[data-voice-draft] .play[data-smooth-corners] > button {
clip-path: none;
}
5 changes: 2 additions & 3 deletions src/features/messages/ComposerAttachments.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
.item {
display: flex;
align-items: center;
gap: calc(var(--space-5) / 2);
gap: calc(var(--space-5) / 2 + var(--space-1));
padding: var(--space-2) calc(var(--space-5) / 2);
margin-block: 0;
box-sizing: border-box;
Expand All @@ -41,8 +41,7 @@
position: relative;
max-width: 100%;
}
.preview,
.fileIcon {
.preview {
width: var(--size-control);
height: var(--size-control);
flex-shrink: 0;
Expand Down
5 changes: 3 additions & 2 deletions src/features/messages/ComposerAttachments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AttachmentView } from "../conversation/AttachmentView";
import { useConversationPresentation } from "../conversation/ConversationPresentation";
import { useRef, useState } from "react";
import { FileTypeIcon } from "./FileTypeIcon";
import fileIconStyles from "./FileTypeIcon.module.css";
import { IconButton } from "../../shared/design-system/ui/IconButton";
import {
AnimatePresence,
Expand Down Expand Up @@ -298,10 +299,10 @@ function AttachmentItem({
)}
</button>
) : (
<span className={styles.fileIcon}>
<span className={fileIconStyles.slot}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 After this switch to fileIconStyles.slot, the .fileIcon rule at ComposerAttachments.module.css:171-180 has no consumer, and it repeats the slot geometry. Please delete it so there is one owner.

<IconButton
type="button"
variant="solid"
variant="bare"
size="compact"
style={{ width: "100%", height: "100%", borderRadius: "inherit" }}
data-draft-preview={item.id}
Expand Down
9 changes: 7 additions & 2 deletions src/features/messages/FileAttachment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
DownloadIcon,
} from "../../shared/design-system/icons/index";
import { FileTypeIcon } from "./FileTypeIcon";
import fileIconStyles from "./FileTypeIcon.module.css";
import type { Attachment } from "../relay/contracts";
import {
isNativeMediaSource,
Expand Down Expand Up @@ -54,7 +55,9 @@ export function FileAttachment({

const content = (
<>
<FileTypeIcon name={attachment.name} mime={attachment.mime} />
<span className={fileIconStyles.slot}>
<FileTypeIcon name={attachment.name} mime={attachment.mime} />
</span>
<span className={styles.fileAttachmentBody}>
<span className={styles.fileAttachmentName}>{displayName}</span>
<span className={styles.fileAttachmentMeta}>
Expand Down Expand Up @@ -140,7 +143,9 @@ function UnavailableFileAttachment({
}) {
return (
<span className={styles.fileAttachment} role="status" title={displayName}>
<FileTypeIcon name={attachment.name} mime={attachment.mime} />
<span className={fileIconStyles.slot}>
<FileTypeIcon name={attachment.name} mime={attachment.mime} />
</span>
<span className={styles.fileAttachmentBody}>
<span className={styles.fileAttachmentName}>{displayName}</span>
<span className={styles.fileAttachmentMeta}>File unavailable</span>
Expand Down
38 changes: 18 additions & 20 deletions src/features/messages/FileTypeIcon.module.css
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
.slot {
display: inline-flex;
flex-shrink: 0;
width: calc(36px + 2 * var(--space-1h));
height: calc(36px + 2 * var(--space-1h));
margin-block: calc(-1 * var(--space-1h));
margin-inline-start: calc(-1 * var(--space-2));
margin-inline-end: calc(-1 * var(--space-1));
border-radius: calc(var(--radius-icon-button) - var(--space-half) - 1px);
}
.icon {
display: inline-grid;
place-items: center;
flex-shrink: 0;
width: calc(var(--space-8) + var(--space-1));
height: calc(var(--space-8) + var(--space-1));
border-radius: var(--radius-control);
background: var(--surface-panel);
width: calc(36px + 2 * var(--space-1h));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 .icon repeats .slot's size and radius, and VoiceNotes.module.css:87-95 repeats all of it again. Suggest one owner: keep the geometry on .slot (or one custom property such as --attachment-tile-size), let .icon fill it with width: 100%; height: 100%; border-radius: inherit, and have the voice .play use the same slot. Today a change to the composer tile size has to be made in three files.

height: calc(36px + 2 * var(--space-1h));
border-radius: calc(var(--radius-icon-button) - var(--space-half) - 1px);
background: color-mix(
in srgb,
var(--surface-inset) 90%,
var(--text-standard)
);
color: var(--text-standard);
}
.icon[data-kind="pdf"] {
background: var(--file-pdf-surface);
color: var(--file-pdf-ink);
}
.icon[data-kind="sheet"] {
background: var(--affordance-success);
color: var(--text-success);
}
.icon[data-kind="archive"] {
background: var(--affordance-warning);
color: var(--text-warning);
}
.icon[data-kind="code"] {
background: var(--affordance-selected);
color: var(--text-link);
}
21 changes: 13 additions & 8 deletions src/features/messages/Messages.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -981,13 +981,15 @@ html[data-keyboard-navigation] .attachmentImage:focus-visible {
text-align: start;
display: inline-flex;
align-items: center;
gap: var(--space-2);
width: 21rem;
gap: calc(var(--space-5) / 2 + var(--space-1));
width: 18rem;
box-sizing: border-box;
min-height: calc(36px + 2 * var(--space-2) + 2px);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Nit: this is the same value as --attachment-chip-height in ComposerAttachments.module.css:12, and the gap formula on line 984 is repeated in the composer item and the voice card. If the sent card should match the composer, one shared value says so in one place.

max-width: 100%;
margin-top: var(--space-2);
padding: var(--space-2) var(--space-3);
border: 1px solid var(--border);
border-radius: var(--radius-container);
padding: var(--space-2) calc(var(--space-5) / 2);
border: 1px solid var(--border-standard);
border-radius: var(--radius-icon-button);
background: var(--surface-inset);
color: var(--text-standard);
text-decoration: none;
Expand Down Expand Up @@ -1024,7 +1026,7 @@ html[data-keyboard-navigation] .fileAttachment:focus-visible {
min-width: 0;
max-width: 100%;
display: grid;
gap: var(--space-half);
gap: 0;
}
.fileAttachmentName,
.fileAttachmentMeta {
Expand All @@ -1033,10 +1035,13 @@ html[data-keyboard-navigation] .fileAttachment:focus-visible {
white-space: nowrap;
}
.fileAttachmentName {
font-weight: var(--type-weight-medium);
font-size: var(--text-label-sm);
font-weight: var(--text-label-sm--font-weight);
line-height: var(--text-label-sm--line-height);
letter-spacing: var(--text-label-sm--letter-spacing);
}
.fileAttachmentMeta {
color: var(--text-muted);
color: var(--text-subtle);
font-size: var(--text-caption);
line-height: var(--text-caption--line-height);
letter-spacing: var(--text-caption--letter-spacing);
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/attachment-playground.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>Buzz · Attachment playground</title></head><body><div id="root"></div><script type="module" src="./attachment-playground/main.tsx"></script></body></html>
37 changes: 37 additions & 0 deletions tests/fixtures/attachment-playground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Attachment playground

Open `/tests/fixtures/attachment-playground.html` on the existing desktop Vite server.
No second server, account, upload, or relay session is started by the page.

Uses production `ComposerAttachments`, composer CSS, `MessageRow`, `AudioAttachment`,
`VoiceNoteCard`, and shared PanelHeader, FullPageSurface, Select, and Button.
Controls compare light/dark, six canonical accents, low/high contrast,
sent/received rows, wide/narrow frames, and 100%/200% text. Theme preferences
are confined to iframe documents and never saved to app preferences.

File groups cover all five FileTypeIcon branches plus office and unknown files,
long names, images/animations, video, audio, voice notes, media-only/mixed strips,
queued/preparing/uploading/ready/error, disabled removal, and missing sources.
Upload states are simulated; retry resolves locally and reset restores samples.
Audio uses a generated two-second tone. Sent audio players are mounted directly
because MessageRow requires a native/broker media source for inline audio.
The animated WebP fixture currently exercises decoder fallback in the in-app
browser. File links are illustrative; normal activation in message rows is intercepted.
Composer review uses production behavior and can offer the illustrative external URL.

Check fixture types with:

```sh
bin/pnpm exec tsc --project tests/fixtures/attachment-playground/tsconfig.json --noEmit
bin/pnpm exec biome check tests/fixtures/attachment-playground tests/fixtures/attachment-playground.html
```

Initial manual checks: light/dark rendering, section selection with keyboard,
retry recovery, removal, narrow frames at 200% text, and all-accent frame inventory.
This fixture does not validate uploads, native IPC, persistence, or full app integration.
Current iteration: monochrome file glyphs sit in neutral 90/10 inset tiles in both
composer and sent-file cards. Sent cards share composer sizing, type, and shape;
voice-note play tiles use the same geometry in drafts and messages. Composer
previews use the shared bare IconButton variant.
Checked this iteration: light/dark composer and sent-file rendering, sent voice-note
keyboard playback, fixture typecheck, formatting, and 37 focused component tests.
Loading
Loading