diff --git a/services/frontend/src/app/(dashboard)/messages/page.tsx b/services/frontend/src/app/(dashboard)/messages/page.tsx
index f24417e..6d98409 100644
--- a/services/frontend/src/app/(dashboard)/messages/page.tsx
+++ b/services/frontend/src/app/(dashboard)/messages/page.tsx
@@ -2,7 +2,7 @@
* Messages — Server Component.
* Reads URL guild/channel/selected/tab on the server; seeds first page SSR.
*/
-import { getMessages } from "@/lib/api/server";
+import { getMessages, type MessagePageResult } from "@/lib/api/server";
import MessagesView from "./view";
export default async function MessagesPage({
@@ -19,7 +19,7 @@ export default async function MessagesPage({
? (sp.tab as "all" | "images" | "review")
: "all";
- let initialPage;
+ let initialPage: MessagePageResult | undefined;
if (guild) {
initialPage = await getMessages(guild, channel || undefined).catch(
() => undefined,
diff --git a/services/frontend/src/app/(dashboard)/recordings/view.tsx b/services/frontend/src/app/(dashboard)/recordings/view.tsx
index f23053e..d306afa 100644
--- a/services/frontend/src/app/(dashboard)/recordings/view.tsx
+++ b/services/frontend/src/app/(dashboard)/recordings/view.tsx
@@ -149,7 +149,13 @@ function PreviewDialog({
{recording.filename}
-
+ {/* biome-ignore lint/a11y/useMediaCaption: voice recordings are uncaptioned audio previews — no transcript available */}
+
{(recording.size_bytes / 1024).toFixed(0)} KB ·{" "}
{recording.upload_status}
diff --git a/services/frontend/src/components/charts/radial-gauge.tsx b/services/frontend/src/components/charts/radial-gauge.tsx
index 3dd22c1..d7a603b 100644
--- a/services/frontend/src/components/charts/radial-gauge.tsx
+++ b/services/frontend/src/components/charts/radial-gauge.tsx
@@ -43,6 +43,8 @@ export function RadialGauge({
height={size}
viewBox={`0 0 ${size} ${size}`}
className="-rotate-90"
+ role="img"
+ aria-label={`${Math.round(pct * 100)}% ${label ?? "gauge"}`}
>
;
+ return (
+
+ );
const min = Math.min(...data);
const max = Math.max(...data);
@@ -46,6 +54,8 @@ export function Sparkline({
viewBox={`0 0 ${width} ${height}`}
className={className}
preserveAspectRatio="none"
+ role="img"
+ aria-label="Trend sparkline"
>
diff --git a/services/frontend/src/components/chatbot/chat-panel.tsx b/services/frontend/src/components/chatbot/chat-panel.tsx
index 272b411..1bff9da 100644
--- a/services/frontend/src/components/chatbot/chat-panel.tsx
+++ b/services/frontend/src/components/chatbot/chat-panel.tsx
@@ -52,15 +52,15 @@ export function ChatPanel({ inputRef: externalInputRef }: ChatPanelProps) {
};
return (
-
+
{/* Chat messages */}
{messages.length === 0 ? (
-
-
+
+
Halo! 👋 Aku tau soal server ini — pesan, flag, dan aktivitas.
@@ -70,9 +70,9 @@ export function ChatPanel({ inputRef: externalInputRef }: ChatPanelProps) {
type="button"
onClick={() => handleSuggestion(s)}
disabled={isTyping}
- className="flex items-center gap-1 rounded-full border border-glass-border px-2.5 py-1 text-[10px] text-text-secondary/70 transition-colors hover:bg-glass-bg hover:text-text-primary disabled:opacity-40"
+ className="flex items-center gap-1 rounded-full border border-[var(--color-hairline)] bg-[var(--color-surface-2)] px-2.5 py-1 text-[10px] text-[var(--color-ink-soft)] transition-colors hover:bg-[var(--color-signal)] hover:text-[var(--color-signal-ink)] disabled:opacity-40"
>
-
+
{s}
))}
@@ -85,15 +85,15 @@ export function ChatPanel({ inputRef: externalInputRef }: ChatPanelProps) {
className={`flex flex-col ${msg.role === "user" ? "items-end" : "items-start"}`}
>
{msg.content}
-
+
{formatTime(msg.timestamp)}
@@ -101,18 +101,18 @@ export function ChatPanel({ inputRef: externalInputRef }: ChatPanelProps) {
)}
{isTyping && (
-
+
@@ -124,13 +124,13 @@ export function ChatPanel({ inputRef: externalInputRef }: ChatPanelProps) {
{/* Input bar */}
diff --git a/services/frontend/src/components/chatbot/chatbot-container.tsx b/services/frontend/src/components/chatbot/chatbot-container.tsx
index 343d056..c798b4b 100644
--- a/services/frontend/src/components/chatbot/chatbot-container.tsx
+++ b/services/frontend/src/components/chatbot/chatbot-container.tsx
@@ -47,50 +47,49 @@ export function ChatbotContainer() {
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
>
- {/* Main chatbot bubble — minimized FAB opens the full chat directly */}
{minimized ? (
) : (
-
+
{/* Drag handle + controls */}
{/* biome-ignore lint/a11y/noStaticElementInteractions: drag handle — mouse-only gesture, keyboard users use the buttons in this header */}
-
-
+
+
Chatbot
{/* Chat panel — always open when bubble is expanded */}
-
diff --git a/services/frontend/src/components/messages/message-entry.tsx b/services/frontend/src/components/messages/message-entry.tsx
index a84206b..614546d 100644
--- a/services/frontend/src/components/messages/message-entry.tsx
+++ b/services/frontend/src/components/messages/message-entry.tsx
@@ -39,11 +39,12 @@ export function MessageEntry({
const status = msg.ai_status ?? null;
return (
-
-
+
);
}
diff --git a/services/frontend/src/components/primitives/tooltip.tsx b/services/frontend/src/components/primitives/tooltip.tsx
index 78c5106..e275755 100644
--- a/services/frontend/src/components/primitives/tooltip.tsx
+++ b/services/frontend/src/components/primitives/tooltip.tsx
@@ -25,6 +25,7 @@ export function Tooltip({
}: TooltipProps) {
const [show, setShow] = useState(false);
return (
+ // biome-ignore lint/a11y/noStaticElementInteractions: tooltip wrapper — reveals on hover AND focus (keyboard-accessible via focus handlers above)
setShow(true)}
diff --git a/services/frontend/src/components/shared/loading-skeleton.tsx b/services/frontend/src/components/shared/loading-skeleton.tsx
index 81019ec..f162090 100644
--- a/services/frontend/src/components/shared/loading-skeleton.tsx
+++ b/services/frontend/src/components/shared/loading-skeleton.tsx
@@ -21,7 +21,7 @@ export function LoadingSkeleton({
+
Speaker orbs
{orbs.map((o, i) => (
+ Signal dots
{dots.map((d, i) => (