feat: v1.0.0 — UX polish, security hardening, code quality
UI/UX: - Bill detail page tab UI (Analysis / Timeline / Votes / Notes) - Topic tag pills on bill detail and listing pages — filtered to known topics, clickable, properly labelled via shared lib/topics.ts - Notes panel always-open in Notes tab; sign-in prompt for guests - Collapsible sidebar with icon-only mode and localStorage persistence - Bills page defaults to has-text filter enabled - Follow mode dropdown transparency fix - Favicon (Landmark icon, blue background) Security: - Fernet encryption for ntfy passwords at rest (app/core/crypto.py) - Separate ENCRYPTION_SECRET_KEY env var; falls back to JWT derivation - ntfy_password no longer returned in GET response — replaced with ntfy_password_set: bool; NotificationSettingsUpdate type for writes - JWT_SECRET_KEY fail-fast on startup if using default placeholder - get_optional_user catches (JWTError, ValueError) only, not Exception Bug fixes & code quality: - Dashboard N+1 topic query replaced with single OR query - notification_utils.py topic follower N+1 replaced with batch query - Note query in bill detail page gated on token (enabled: !!token) - search.py max_length=500 guard against oversized queries - CollectionCreate.validate_name wired up with @field_validator - LLM_RATE_LIMIT_RPM default raised from 10 to 50 Authored by: Jack Levy
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { AlertTriangle, CheckCircle, Clock, Cpu, ExternalLink, Tag } from "lucide-react";
|
||||
import { AlertTriangle, CheckCircle, Clock, Cpu, ExternalLink } from "lucide-react";
|
||||
import { BriefSchema, CitedPoint } from "@/lib/types";
|
||||
import { formatDate } from "@/lib/utils";
|
||||
|
||||
@@ -30,28 +30,32 @@ function CitedItem({ point, icon, govinfo_url, openKey, activeKey, setActiveKey
|
||||
<li className="text-sm">
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="mt-0.5 shrink-0">{icon}</span>
|
||||
<span className="flex-1">{cited ? point.text : point}</span>
|
||||
{cited && point.label === "inference" && (
|
||||
<span
|
||||
title="This point is an analytical interpretation, not a literal statement from the bill text"
|
||||
className="shrink-0 text-[10px] px-1.5 py-0.5 rounded border border-border text-muted-foreground font-sans leading-none"
|
||||
>
|
||||
Inferred
|
||||
</span>
|
||||
)}
|
||||
{cited && (
|
||||
<button
|
||||
onClick={() => setActiveKey(isOpen ? null : openKey)}
|
||||
title={isOpen ? "Hide source" : "View source"}
|
||||
className={`shrink-0 text-xs px-1.5 py-0.5 rounded font-mono transition-colors ${
|
||||
isOpen
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
§ {point.citation}
|
||||
</button>
|
||||
)}
|
||||
<div className="flex-1 min-w-0 space-y-1">
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="flex-1">{cited ? point.text : point}</span>
|
||||
{cited && point.label === "inference" && (
|
||||
<span
|
||||
title="This point is an analytical interpretation, not a literal statement from the bill text"
|
||||
className="shrink-0 text-[10px] px-1.5 py-0.5 rounded border border-border text-muted-foreground font-sans leading-none mt-0.5"
|
||||
>
|
||||
Inferred
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{cited && (
|
||||
<button
|
||||
onClick={() => setActiveKey(isOpen ? null : openKey)}
|
||||
title={isOpen ? "Hide source" : "View source"}
|
||||
className={`text-left text-xs px-1.5 py-0.5 rounded font-mono leading-snug transition-colors ${
|
||||
isOpen
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
§ {point.citation}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{cited && isOpen && (
|
||||
<div className="mt-2 ml-5 rounded-md border border-border bg-muted/40 p-3 space-y-2">
|
||||
@@ -165,19 +169,6 @@ export function AIBriefCard({ brief }: AIBriefCardProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{brief.topic_tags && brief.topic_tags.length > 0 && (
|
||||
<div className="flex items-center gap-2 pt-1 border-t border-border flex-wrap">
|
||||
<Tag className="w-3.5 h-3.5 text-muted-foreground shrink-0" />
|
||||
{brief.topic_tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="text-xs px-2 py-1 bg-accent text-accent-foreground rounded-full"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user