feat(paliadin/agent-glyph): t-paliad-161 Slice E — alongside 👀

When a pending row was drafted by Paliadin (requester_kind='agent' on
its in-flight approval_request), surface a sparkle  next to the
existing eye-pill 👀. The two glyphs are orthogonal: 👀 = "needs
approval",  = "Paliadin drafted this". Either can change without the
other, so the visual taxonomy stays decomposable for any future
autopilot mode where 👀 disappears but  stays.

Read-path:

- DeadlineService.ListVisibleForUser + AppointmentService.ListVisibleForUser
  LEFT JOIN paliad.approval_requests on pending_request_id and project
  ar.requester_kind into the row. NULL when no request is pending.
- models.DeadlineWithProject + AppointmentWithProject grow
  RequesterKind *string. The list-projection helpers
  (projectDeadline / projectAppointment in event_service.go) carry it
  into EventListItem.
- /api/events response now includes requester_kind on every pending
  row; /api/inbox already does (Slice D extended approvalRequestViewColumns).

Render-path:

- frontend/src/client/events.ts — new AGENT_PILL_GLYPH constant (""),
  agentPill rendered into the title cell next to the existing
  pendingPill when item.approval_status='pending' AND
  item.requester_kind='agent'. EventListItem TS shape gains
  `requester_kind?: "user" | "agent"`.
- frontend/src/client/agenda.ts — same pattern, agendaItem TS shape
  + agentPill rendered next to pendingPill in the headline span.
- frontend/src/client/inbox.ts — ApprovalRequestView gains
  requester_kind + agent_turn_id; the meta line replaces the
  requester's plain name with "Anna  Paliadin" when the request was
  drafted by the agent.

CSS: new .approval-pill--agent modifier in global.css using only
existing tokens (--color-bg-lime-tint / --color-surface-2 /
--color-text), mirroring the .approval-pill--icon shape so the two
glyphs sit side-by-side at the same baseline.

i18n: 3 new keys × 2 langs (approvals.agent.label /
approvals.agent.byline / approvals.agent.suggestion_pending) — total
1966 → 1969.

Build clean (frontend + go), tests green.

Refs: docs/design-paliadin-inline-2026-05-08.md §8.
This commit is contained in:
m
2026-05-08 20:04:10 +02:00
parent a3052eb085
commit 4ecea7a4bb
10 changed files with 87 additions and 4 deletions

View File

@@ -185,9 +185,11 @@ func (s *AppointmentService) ListVisibleForUser(ctx context.Context, userID uuid
t.approval_status, t.pending_request_id, t.approved_by, t.approved_at,
p.reference AS project_reference,
p.title AS project_title,
p.type AS project_type
p.type AS project_type,
ar.requester_kind AS requester_kind
FROM paliad.appointments t
LEFT JOIN paliad.projects p ON p.id = t.project_id
LEFT JOIN paliad.approval_requests ar ON ar.id = t.pending_request_id
WHERE ` + strings.Join(conds, " AND ") + `
ORDER BY t.start_at ASC, t.created_at DESC`

View File

@@ -240,10 +240,12 @@ func (s *DeadlineService) ListVisibleForUser(ctx context.Context, userID uuid.UU
p.title AS project_title,
p.type AS project_type,
r.name AS rule_name,
r.name_en AS rule_name_en
r.name_en AS rule_name_en,
ar.requester_kind AS requester_kind
FROM paliad.deadlines f
JOIN paliad.projects p ON p.id = f.project_id
LEFT JOIN paliad.deadline_rules r ON r.id = f.rule_id
LEFT JOIN paliad.approval_requests ar ON ar.id = f.pending_request_id
WHERE ` + strings.Join(conds, " AND ") + `
ORDER BY f.due_date ASC, f.created_at DESC`

View File

@@ -97,6 +97,10 @@ type EventListItem struct {
// (default), "pending" (in-flight 4-eye request — pill rendered on
// every list surface), or "legacy" (pre-4-eye row, no pill).
ApprovalStatus *string `json:"approval_status,omitempty"`
// RequesterKind is the kind of the in-flight approval request when
// approval_status='pending': 'user' or 'agent' (Paliadin-drafted —
// t-paliad-161). NULL otherwise. Drives the ✨ glyph alongside 👀.
RequesterKind *string `json:"requester_kind,omitempty"`
// Deadline-only.
DueDate *string `json:"due_date,omitempty"` // YYYY-MM-DD
@@ -223,6 +227,7 @@ func projectDeadline(d models.DeadlineWithProject) EventListItem {
ProjectType: &ptype,
CreatedBy: d.CreatedBy,
ApprovalStatus: &approvalStatus,
RequesterKind: d.RequesterKind,
DueDate: &due,
Status: &status,
CompletedAt: d.CompletedAt,
@@ -251,6 +256,7 @@ func projectAppointment(a models.AppointmentWithProject) EventListItem {
ProjectType: a.ProjectType,
CreatedBy: a.CreatedBy,
ApprovalStatus: &approvalStatus,
RequesterKind: a.RequesterKind,
StartAt: &startCopy,
EndAt: a.EndAt,
Location: a.Location,