Files
pad/internal/models/needs.go
T
xarmian eb7f8a200c feat(needs): per-user dismiss/defer storage and the needs/snooze routes (TASK-3918) (#2231)
IDEA-3906 half A, unit A2.

- Migrations 153 (SQLite) and 122 (Postgres) add item_needs_snoozes, one
  row per (user, item), FKs cascading, indexed on item_id.
- A deferral holds until defer_until (an RFC3339 time after now and within
  366 days), whatever changes before then (IDEA-3906 A2). A fractional time
  is stored rounded up to the next whole second, never early.
- A dismissal holds until something happens after its marker: a live
  comment by anyone else newer than seen_comment_at, another such comment
  at that same second (seen_comment_ids), or a later status transition
  (MAX(status_transitions.seq); items.seq and updated_at don't move on a
  comment). The user's own human comments never break it; an agent's
  comments under their credentials do, and so does the user's own status
  change. Imported comments are history and never break it.
- The marker comes from the client, clamped to the server's values so it
  can only claim less was seen. No marker means the current state.
- PUT|DELETE /api/v1/workspaces/{ws}/items/{slug}/needs/snooze, beside
  stars and watches: requireItemVisible (404 when hidden) and no edit
  check, so viewers and item-grant guests may snooze. Read-scoped tokens
  are refused by tokenScopeAllows. DELETE is idempotent.
- nulcolumns classifies the table's TEXT columns as server-written.
- A migrate-to-pg marked SQLite file refuses snooze writes: the table joins
  migratedRefusalTables, since models.WorkspaceExport does not carry it.
  TestMigratedDatabaseRefusesNeedsSnoozeWrites drives the store's own verbs
  rather than iterating the list, so removing the entry goes red.
- Workspace purge relies on the FK cascade; the purge census
  (wsChildTables, seedFullWorkspace) now counts item_needs_snoozes.
- docs/api.md documents the endpoint.

Claude-Session: https://claude.ai/code/session_018oQ5EErQKHtBJK8yLpcDNF
2026-10-19 07:02:00 -04:00

32 lines
1.3 KiB
Go

package models
// NeedsSnoozeMarker is what a person had seen of an item when they dismissed
// it from their needs-you inbox (TASK-3918, IDEA-3906): the newest comment
// by anyone else, the ids of the comments at that same second, and the
// item's latest status-transition seq. The server clamps a client's marker
// to its own values, so a marker can only ever claim LESS was seen.
type NeedsSnoozeMarker struct {
SeenCommentAt string `json:"seen_comment_at"`
SeenCommentIDs []string `json:"seen_comment_ids"`
SeenTransitionSeq int64 `json:"seen_transition_seq"`
}
// NeedsSnooze is one person's dismissal or deferral of one item.
type NeedsSnooze struct {
ItemID string `json:"item_id"`
WorkspaceID string `json:"workspace_id"`
Kind string `json:"kind"` // NeedsSnoozeDismiss or NeedsSnoozeDefer
DeferUntil string `json:"defer_until,omitempty"`
Marker NeedsSnoozeMarker `json:"marker"`
CreatedAt string `json:"created_at"`
// Holds reports whether the snooze still hides the item, computed at
// read time: a deferral until its date, a dismissal until something
// happens on the item after the marker.
Holds bool `json:"holds"`
}
const (
NeedsSnoozeDismiss = "dismiss"
NeedsSnoozeDefer = "defer"
)