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
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,14 @@ route guards). To hit a real API in dev, run the backend and set `VITE_API_PROXY
- Log every silent failure: a `catch` may swallow only if the caller surfaces the
failure another way (an `errors[]` roll-up, a non-2xx result); otherwise it must
log (server: `ILogger`; degradations that report "success" hide the cause).
- Comments explain **why**, never restate **what**. A comment may record a
decision, a constraint, a non-obvious consequence, a units/ordering/vocabulary
contract, or a domain fact not visible in the code. It must not paraphrase the
line below it — that's two statements of one fact with nothing keeping them in
sync (this concealed a real authorization bug here — a comment claimed a gate
the code didn't have). If a comment is needed just to say *what* the code does,
**rename the identifier** until it isn't. Treat a comment that contradicts its
code as a suspected bug (fix the code, not the comment), not a style nit.

**Don't**
- Don't redesign, restyle, or add screens/sections not in the prototype —
Expand Down
2 changes: 0 additions & 2 deletions server/Integrations/Jira.cs
Original file line number Diff line number Diff line change
Expand Up @@ -561,15 +561,13 @@ public static async Task<int> PullOpsItemsAsync(AtlasDbContext db, IConfiguratio
return imported;
}

// Jira issue type → Ops work-item type.
static string MapOpsType(string? t) => (t ?? "").ToLowerInvariant() switch
{
"bug" or "incident" or "fault" => "Incident",
"change" or "change request" => "Change",
"task" or "story" or "sub-task" or "subtask" => "Request",
_ => "Other",
};
// Jira status category → Ops work-item status.
static string MapOpsStatus(string? cat) => (cat ?? "").ToLowerInvariant() switch
{
"done" => "Done",
Expand Down
1 change: 0 additions & 1 deletion server/Kernel/Dtos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ public record OpsServiceDto(int Id, string Ref, string Name, string Category, st
bool Archived = false, string JiraProjectKey = "", List<OpsLinkedTaskDto>? LinkedTasks = null);
public record OpsSummaryDto(int Services, int OpenItems, int Blocked, int ImpactedProjects, int PeopleEngaged);
public record OpsBoardDto(bool CanEdit, List<OpsServiceDto> Services, OpsSummaryDto Summary);
// Ops load impacting a single project (shown on Project Detail).
public record OpsImpactRowDto(int Id, string Title, string ServiceName, string Type, string Priority,
string Status, string Assignee, int Alloc, string ImpactNote);
public record OpsImpactDto(int Alloc, List<OpsImpactRowDto> Items);
Expand Down
1 change: 0 additions & 1 deletion src/components/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export function ProgressBar({ pct, fill = color.primary, height = 6, track = cha
);
}

// Small empty-state block shown inside a card body when there's no data.
export function EmptyBlock({ message, minHeight = 96 }: { message: string; minHeight?: number }) {
return (
<div style={{
Expand Down
2 changes: 1 addition & 1 deletion src/components/useRoleIdentities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ROLES, type RoleIdentity } from "@/nav";
interface RoleRow { id: string; name: string; short?: string; who?: string; }
interface RolesMatrix { roles: RoleRow[]; }

// Two-letter initials from a display name (first + last word, else first two chars).
// first + last word's initials, else the first two chars.
function initialsOf(s: string): string {
const parts = s.trim().split(/\s+/).filter(Boolean);
const ini = parts.length > 1
Expand Down
1 change: 0 additions & 1 deletion src/realtime/Presence.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { color } from "@/theme";
import { Icon } from "@/components/Icon";
import type { Peer, PeerCursor } from "./useRoomRealtime";

// A small "Live"/"Offline" pill reflecting the hub connection.
export function LiveDot({ connected }: { connected: boolean }) {
return (
<div style={{ display: "flex", alignItems: "center", gap: 7, fontSize: 12.5, fontWeight: 600, color: connected ? color.successInk : color.faint }}>
Expand Down
3 changes: 1 addition & 2 deletions src/screens/gantt/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface Win { start: number; span: number }
// Current month as an absolute month (impure — reads the clock).
export const nowAbs = (): number => { const d = new Date(); return d.getFullYear() * 12 + d.getMonth(); };

// Month-of-year (0..11) of an ISO date, or null if empty/invalid.
// 0-based (0..11); null if the date is empty/invalid.
export const monthOfIso = (s: string): number | null => {
if (!s) return null;
const d = new Date(s);
Expand All @@ -43,7 +43,6 @@ export const absToYm = (abs: number): string =>
// Short month name of an absolute month (wraps negatives correctly).
export const monthAbbr = (abs: number): string => MONTHS[((abs % 12) + 12) % 12];

// Calendar year of an absolute month.
export const yearOf = (abs: number): number => Math.floor(abs / 12);

// Anchor a bare month-of-year to a base absolute month (its context's start).
Expand Down
2 changes: 0 additions & 2 deletions src/screens/project/Quality.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ export function Quality({ projectId }: { projectId: string | null }) {
);
}

// A short date like "12 Mar" from an ISO date, or "" — for the compact task row.
function shortDate(iso: string) {
if (!iso) return "";
const d = new Date(iso);
Expand All @@ -154,7 +153,6 @@ function PlanTasks({ projectId, plan, canEdit }: { projectId: string; plan: Test
const [modal, setModal] = useState(false);
const [openTask, setOpenTask] = useState<PlanTask | null>(null);
const invalidate = () => qc.invalidateQueries({ queryKey: ["quality", projectId] });
// Quick inline status change without opening the full window.
const setStatus = useMutation({
mutationFn: (v: { id: number; status: string }) => api(`/test-plan-tasks/${v.id}`, { method: "PATCH", body: JSON.stringify({ status: v.status }) }),
onSuccess: invalidate, onError: (e) => toastError(e),
Expand Down
8 changes: 2 additions & 6 deletions src/whiteboard/Whiteboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,7 @@ export default function Whiteboard({ scope }: { scope: { kind: string; id: strin
}
};

// Start a drag-to-connect wire from a node's connector handle.
const startWire = (e: React.PointerEvent, nodeId: string) => {
const startWire =(e: React.PointerEvent, nodeId: string) => {
e.stopPropagation();
if (!canEdit) return;
wire.current = { from: nodeId }; interacting.current = true;
Expand Down Expand Up @@ -378,8 +377,7 @@ export default function Whiteboard({ scope }: { scope: { kind: string; id: strin
} else if (selEdge) { setScene((s) => removeEdge(s, selEdge)); pushDelEdge(selEdge); setSelEdge(null); }
}, [selIds, selEdge, pushDelNode, pushDelEdge, clearSel]);

// Pick a shape/pen/connector tool (clears the others).
const armShape = (k: NodeKind) => { setPending({ kind: k }); setTool("select"); setLinkFrom(null); setIconMenu(false); };
const armShape =(k: NodeKind) => { setPending({ kind: k }); setTool("select"); setLinkFrom(null); setIconMenu(false); };
const armIcon = (icon: string) => { setPending({ kind: "icon", icon }); setTool("select"); setLinkFrom(null); setIconMenu(false); };
const armTool = (t: "select" | "connector" | "pen") => { setTool(t); setPending(null); setLinkFrom(null); setIconMenu(false); };

Expand Down Expand Up @@ -737,7 +735,6 @@ function NodeView({ node, selected, canEdit, editing, linkSource, onSelect, onDo
);
}

// Flatten [x0,y0,x1,y1,…] into an SVG points attribute.
function pointsAttr(pts: number[]): string {
let s = "";
for (let i = 0; i + 1 < pts.length; i += 2) s += `${pts[i]},${pts[i + 1]} `;
Expand All @@ -764,7 +761,6 @@ function ActorGlyph({ color: c }: { color: string }) {
);
}

// Tiny glyph shown on each shape tool button.
function ShapeGlyph({ kind }: { kind: NodeKind }) {
const s = 16;
const outline = `2px solid ${color.text}`;
Expand Down
2 changes: 0 additions & 2 deletions src/whiteboard/exportScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ function nodeSvg(n: WbNode): string {
}
}

// Build a standalone SVG string for the whole scene, tightly cropped with a margin.
export function sceneToSvg(scene: Scene): string {
const M = 40;
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
Expand Down Expand Up @@ -116,7 +115,6 @@ export function jsonToScene(text: string): Scene {
return { nodes: Array.isArray(raw.nodes) ? raw.nodes : [], edges: Array.isArray(raw.edges) ? raw.edges : [] };
}

// Trigger a browser download of a blob.
function download(name: string, blob: Blob) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
Expand Down
2 changes: 1 addition & 1 deletion src/whiteboard/scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function createStroke(points: number[], color: string): WbNode {
};
}

// Translate a node by (dx,dy); for freehand nodes the polyline points move too.
// Freehand nodes also shift their polyline points, not just x/y.
export function translateNode(node: WbNode, dx: number, dy: number): Partial<WbNode> {
if (node.kind === "draw" && node.points) {
return { x: node.x + dx, y: node.y + dy, points: node.points.map((v, i) => v + (i % 2 === 0 ? dx : dy)) };
Expand Down
Loading