diff --git a/src/workspaces/officerdev/src/apps/Gitea/DashboardViews.tsx b/src/workspaces/officerdev/src/apps/Gitea/DashboardViews.tsx
index 43a73550..073c1c9b 100644
--- a/src/workspaces/officerdev/src/apps/Gitea/DashboardViews.tsx
+++ b/src/workspaces/officerdev/src/apps/Gitea/DashboardViews.tsx
@@ -129,7 +129,7 @@ export const NotificationsView = () => {
const to = subjectRoute(notification);
const inner = (
<>
-
{notification.subject.title}
@@ -236,7 +236,7 @@ export const OrganizationsView = () => {
{org.full_name || org.username}
{org.description &&{org.description}
} -+
@{org.username}
{org.visibility && ` · ${org.visibility}`}
{org.location && ` · ${org.location}`}
diff --git a/src/workspaces/officerdev/src/apps/Gitea/GiteaBits.tsx b/src/workspaces/officerdev/src/apps/Gitea/GiteaBits.tsx
index 1b23dcbe..b8d6d94a 100644
--- a/src/workspaces/officerdev/src/apps/Gitea/GiteaBits.tsx
+++ b/src/workspaces/officerdev/src/apps/Gitea/GiteaBits.tsx
@@ -1,28 +1,47 @@
+import type { Tone } from '@/components/Data';
import type { ReactNode } from 'react';
import type { GiteaIssue, GiteaLabel, GiteaPullRequest, GiteaUser } from './shared';
-import {
- CircleDot,
- CircleCheck,
- GitMerge,
- GitPullRequest,
- GitPullRequestClosed,
- Loader2,
- TriangleAlert,
-} from 'lucide-react';
+import { CircleCheck, CircleDot, GitMerge, GitPullRequest, GitPullRequestClosed } from 'lucide-react';
+import { EmptyBlock, ErrorBlock, LoadingBlock, StatusIcon, StatusPill } from '@/components/Data';
import { serviceErrorMessage } from '../../hooks/useServiceConnection';
import { labelTextColor } from './shared';
-// The small pieces every Gitea view repeats: an avatar, a label chip, a state pill, and the three
-// non-content states (loading / failed / empty). Kept together so the repo, issue and PR screens look
-// like one app rather than three, and so a change to "what an error looks like" is one edit.
+// The small pieces every Gitea view repeats. These are now thin adapters over `components/Data` —
+// they translate Gitea's vocabulary (open / closed / merged) into the design language's five tones
+// and otherwise get out of the way. See docs/design-language-interface.md.
+//
+// The export names are unchanged so the ten call sites did not all have to move at once.
+
+/**
+ * Gitea's state vocabulary, mapped once.
+ *
+ * The non-obvious choice is CLOSED ISSUE → neutral rather than a colour of its own. Most issues in
+ * any list are closed; giving them a tone paints most of the screen and leaves the open ones no
+ * quieter than the rest. Neutral is what makes "open" findable — which is the only thing anyone is
+ * scanning an issue list for. A closed PULL request is different: it was rejected, and that is a
+ * real outcome worth marking, so it keeps `danger`.
+ */
+function stateOf(item: GiteaIssue | GiteaPullRequest): { tone: Tone; icon: typeof CircleDot; text: string } {
+ const isPull = 'base' in item || !!(item as GiteaIssue).pull_request;
+ const merged = (item as GiteaPullRequest).merged || (item as GiteaIssue).pull_request?.merged;
+
+ if (merged) return { tone: 'info', icon: GitMerge, text: 'Merged' };
+ if (item.state === 'open') {
+ return { tone: 'success', icon: isPull ? GitPullRequest : CircleDot, text: 'Open' };
+ }
+ return isPull
+ ? { tone: 'danger', icon: GitPullRequestClosed, text: 'Closed' }
+ : { tone: 'neutral', icon: CircleCheck, text: 'Closed' };
+}
export const Avatar = ({ user, size = 20 }: { user?: GiteaUser; size?: number }) => {
const initial = (user?.full_name || user?.login || '?').charAt(0).toUpperCase();
if (!user?.avatar_url) {
return (
{initial}
@@ -32,6 +51,7 @@ export const Avatar = ({ user, size = 20 }: { user?: GiteaUser; size?: number })
(
@@ -49,79 +73,43 @@ export const LabelChip = ({ label }: { label: GiteaLabel }) => (
);
-/**
- * The open/closed/merged pill. Merged is a genuinely distinct state from closed and Gitea colours it
- * differently — a merged PR is not a rejected one — so it is worth the extra branch here.
- */
export const StateBadge = ({ item }: { item: GiteaIssue | GiteaPullRequest }) => {
- const isPull = 'base' in item || !!(item as GiteaIssue).pull_request;
- const merged = (item as GiteaPullRequest).merged || (item as GiteaIssue).pull_request?.merged;
- const open = item.state === 'open';
-
- const [Icon, text, classes] = merged
- ? [GitMerge, 'Merged', 'bg-purple-500/15 text-purple-500']
- : open
- ? isPull
- ? [GitPullRequest, 'Open', 'bg-emerald-500/15 text-emerald-500']
- : [CircleDot, 'Open', 'bg-emerald-500/15 text-emerald-500']
- : isPull
- ? [GitPullRequestClosed, 'Closed', 'bg-red-500/15 text-red-500']
- : [CircleCheck, 'Closed', 'bg-purple-500/15 text-purple-500'];
-
+ const { tone, icon, text } = stateOf(item);
return (
-
-
{title}
-{serviceErrorMessage(error)}
-{title}
- {hint &&{hint}
} -