Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
54fb59d
docs(regulations-tracker): add design spec
gorkem-bwl Jun 26, 2026
0e03256
docs(regulations-tracker): add implementation plan
gorkem-bwl Jun 26, 2026
194f4da
feat(regulations-tracker): add module tables migration
gorkem-bwl Jun 26, 2026
d0d8d71
feat(regulations-tracker): add interface and Sequelize models
gorkem-bwl Jun 26, 2026
7d340e7
feat(regulations-tracker): seed country catalog snapshot
gorkem-bwl Jun 26, 2026
911c95b
feat(regulations-tracker): add feed fetch and validation
gorkem-bwl Jun 26, 2026
42714eb
feat(regulations-tracker): add rendering, week key, and feed upsert
gorkem-bwl Jun 26, 2026
fc70488
feat(regulations-tracker): append CRUD + recipient-resolution utils (…
gorkem-bwl Jun 26, 2026
1302b75
feat(regulations-tracker): add email digest template
gorkem-bwl Jun 26, 2026
4213acf
feat(regulations-tracker): add weekly sync job (task 8)
gorkem-bwl Jun 26, 2026
899d249
fix(regulations-tracker): use change lines in digest + add sync job t…
gorkem-bwl Jun 26, 2026
8f71595
feat(regulations-tracker): schedule weekly sync job
gorkem-bwl Jun 26, 2026
242268c
feat(regulations-tracker): add routes and controllers
gorkem-bwl Jun 26, 2026
016359e
test(regulations-tracker): add endpoint integration tests
gorkem-bwl Jun 26, 2026
6358d55
test(regulations-tracker): strengthen controller test assertions
gorkem-bwl Jun 26, 2026
eca24ce
feat(regulations-tracker): add frontend repository and hooks
gorkem-bwl Jun 26, 2026
c2b67f3
feat(regulations-tracker): add Browse/Tracked/Settings/Detail pages
gorkem-bwl Jun 26, 2026
993d3cd
fix(regulations-tracker): use theme tokens + pixel spacing in pages
gorkem-bwl Jun 26, 2026
2ea7c02
feat(regulations-tracker): register routes and navigation
gorkem-bwl Jun 26, 2026
2411dd2
fix(regulations-tracker): add de/fr/es nav translations + format
gorkem-bwl Jun 26, 2026
9dfad4d
fix(regulations-tracker): compute is_tracked server-side + unify deta…
gorkem-bwl Jun 26, 2026
480eebd
fix(regulations-tracker): fix blocking correctness + should-fix issues
gorkem-bwl Jun 26, 2026
67e3d17
fix(regulations-tracker): cleanup and quality fixes
gorkem-bwl Jun 26, 2026
bbb74d6
fix(regulations-tracker): store full country detail so detail pages r…
gorkem-bwl Jun 26, 2026
fb14574
style(regulations-tracker): prettier formatting for repo/hooks/context
gorkem-bwl Jun 26, 2026
9a7b72d
feat(regulations-tracker): country flags, tracked tick, row spacing
gorkem-bwl Jun 26, 2026
4ef7c74
feat(regulations-tracker): show full country detail (overview + compl…
gorkem-bwl Jun 26, 2026
64cd035
i18n(regulations-tracker): translate Horizon/Deadlines/Frameworks str…
gorkem-bwl Jun 26, 2026
2eb8357
feat(regulations-tracker): add Horizon, Deadlines, and Frameworks tabs
gorkem-bwl Jun 26, 2026
1f01c79
fix(regulations-tracker): match tracked tick size to checkbox footprint
gorkem-bwl Jun 26, 2026
7f4c6b5
feat(regulations-tracker): use VWLink with persistent arrow for sourc…
gorkem-bwl Jun 26, 2026
72186af
feat(regulations-tracker): render lastVerified, dateConfidence, frame…
gorkem-bwl Jun 26, 2026
12ac3fe
feat(regulations-tracker): deep-linked notifications, change detail i…
gorkem-bwl Jun 26, 2026
42c702b
feat(regulations-tracker): multi-change note, new-country alerts, on-…
gorkem-bwl Jun 27, 2026
8d823d9
test(regulations-tracker): unit-test countChangesSince hashHistory diff
gorkem-bwl Jun 27, 2026
d93826c
fix(regulations-tracker): address code-review findings in update work…
gorkem-bwl Jun 27, 2026
48abc43
docs(regulations-tracker): add technical reference + session handover
gorkem-bwl Jun 27, 2026
e511ad0
docs(regulations-tracker): design spec for regulation impact analysis…
gorkem-bwl Jun 27, 2026
c113067
docs(regulations-tracker): close impact-analysis spec gaps from code …
gorkem-bwl Jun 27, 2026
3d6f6c6
docs(regulations-tracker): implementation plan for regulation impact …
gorkem-bwl Jun 27, 2026
7cf5aec
docs(regulations-tracker): add impact-analysis settings (toggle, last…
gorkem-bwl Jun 27, 2026
9554c06
feat(regulations-tracker): add regulation_impact_analysis table
gorkem-bwl Jun 27, 2026
5d54cab
feat(regulations-tracker): impact analysis pure helpers (region/frame…
gorkem-bwl Jun 27, 2026
9c7bbb7
feat(regulations-tracker): Stage A deterministic candidate queries
gorkem-bwl Jun 27, 2026
03918fc
fix(regulations-tracker): remove Stage A no-op queries; verify vendor…
gorkem-bwl Jun 27, 2026
9923235
feat(regulations-tracker): Stage B prompt build + per-type LLM analyze
gorkem-bwl Jun 27, 2026
7a4a241
fix(regulations-tracker): await logFailure in analyzeType for convent…
gorkem-bwl Jun 27, 2026
b4b2679
feat(regulations-tracker): impact analysis orchestrator + persistence
gorkem-bwl Jun 27, 2026
26c839d
fix(regulations-tracker): persist regulation_hash on impact upsert; g…
gorkem-bwl Jun 27, 2026
28df6d4
feat(regulations-tracker): settings support for impact toggle, last-r…
gorkem-bwl Jun 27, 2026
47b4113
feat(regulations-tracker): run impact analysis during sync; nudge key…
gorkem-bwl Jun 27, 2026
1552895
feat(regulations-tracker): impact analysis GET + admin refresh contro…
gorkem-bwl Jun 27, 2026
8891ce4
test(regulations-tracker): assert impact staleness flag both directions
gorkem-bwl Jun 27, 2026
4742b10
feat(regulations-tracker): register impact analysis routes (ordered b…
gorkem-bwl Jun 27, 2026
dba5c17
feat(regulations-tracker): impact analysis repository + hooks
gorkem-bwl Jun 27, 2026
1a67a7e
feat(regulations-tracker): impact panel on country detail page
gorkem-bwl Jun 27, 2026
a31706a
refactor(regulations-tracker): pixel-string margin + hoist impact gro…
gorkem-bwl Jun 27, 2026
70604a5
feat(regulations-tracker): settings toggle + llm-key status + last-im…
gorkem-bwl Jun 27, 2026
61031cf
fix(regulations-tracker): disable impact toggle for non-admins
gorkem-bwl Jun 27, 2026
f4e1aa8
docs(regulations-tracker): document impact analysis (table, endpoints…
gorkem-bwl Jun 27, 2026
6a87cc9
fix(regulations-tracker): honor impact toggle on refresh; cap impact …
gorkem-bwl Jun 27, 2026
8f003ab
style(regulations-tracker): prettier reflow of impact repository methods
gorkem-bwl Jun 27, 2026
6070a85
fix(regulations-tracker): correctness fixes from code review (cache p…
gorkem-bwl Jun 27, 2026
a191f07
fix(regulations-tracker): exclude no-LLM passes from per-run cap; con…
gorkem-bwl Jun 27, 2026
fd8b2a1
fix(regulations-tracker): SPA navigation for configure-key link; 30px…
gorkem-bwl Jun 27, 2026
d618b2f
style(regulations-tracker): prettier format-check fixes across module
gorkem-bwl Jun 27, 2026
da95837
feat(regulations-tracker): explain impact-analysis toggle with plain-…
gorkem-bwl Jun 27, 2026
ce95d07
feat(regulations-tracker): show country flags on the deadlines page
gorkem-bwl Jun 27, 2026
da99016
feat(regulations-tracker): show which LLM key impact analysis uses an…
gorkem-bwl Jun 27, 2026
044c8f9
feat(regulations-tracker): add next-12-months runway calendar to dead…
gorkem-bwl Jun 27, 2026
2f8b65f
docs(regulations-tracker): regenerate API spec for impact endpoints
gorkem-bwl Jun 28, 2026
0ed4ba8
feat(regulations-tracker): color status chips by content across all p…
gorkem-bwl Jun 28, 2026
6ae747f
fix(regulations-tracker): runway calendar — remove today rail, lighte…
gorkem-bwl Jun 28, 2026
9f69deb
feat(regulations-tracker): two-column framework grid and EU AI Act di…
gorkem-bwl Jun 28, 2026
00864af
feat(regulations-tracker): staged progress display for check-for-updates
gorkem-bwl Jun 28, 2026
8201398
fix(regulations-tracker): backfill country flags + fix deadlines flag…
gorkem-bwl Jun 28, 2026
99f6f46
fix(regulations-tracker): replace broken ANY(:param) with IN in Stage…
gorkem-bwl Jun 28, 2026
2865135
feat(regulations-tracker): align browse rows and show regulation coun…
gorkem-bwl Jun 28, 2026
ed2fd94
fix(regulations-tracker): address code-review findings (runway jump, …
gorkem-bwl Jun 28, 2026
7b43082
feat(regulations-tracker): connected vertical timeline on country detail
gorkem-bwl Jun 28, 2026
592537a
fix(regulations-tracker): equal-size browse row checkboxes for tracke…
gorkem-bwl Jun 28, 2026
b7c8597
feat(regulations-tracker): switch sync from weekly to daily cadence
gorkem-bwl Jun 28, 2026
59360ec
docs(regulations-tracker): add user-guide articles for all sidebar pages
gorkem-bwl Jun 28, 2026
c4f9097
feat(regulations-tracker): allow editors to track countries; gate bro…
gorkem-bwl Jun 28, 2026
baa3a15
fix(regulations-tracker): de-weekly the email digest subject
gorkem-bwl Jun 28, 2026
13382eb
feat(regulations-tracker): friendlier empty states + Deadlines countr…
gorkem-bwl Jun 28, 2026
b73321b
feat(regulations-tracker): move impact analysis into the email digest
gorkem-bwl Jun 28, 2026
8088a27
fix(regulations-tracker): show read-only view for super-admins viewin…
gorkem-bwl Jun 28, 2026
c00b30c
docs(regulations-tracker): session handover (2026-06-28)
gorkem-bwl Jun 28, 2026
6988fef
feat(regulations-tracker): add feed-quality and impact-outcome teleme…
gorkem-bwl Jun 28, 2026
2bbd710
fix(regulations-tracker): skip impact analysis when the feed gives no…
gorkem-bwl Jun 28, 2026
e2b0785
fix(regulations-tracker): UI polish across frameworks, tracked, bread…
gorkem-bwl Jun 28, 2026
9c0d4b3
fix(regulations-tracker): capture all change types and scope obligati…
gorkem-bwl Jun 28, 2026
bf07215
fix(regulations-tracker): harden sync, impact analysis, controller an…
gorkem-bwl Jun 29, 2026
c8d5f7d
fix(regulations-tracker): persist country flags across every sync
gorkem-bwl Jun 29, 2026
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
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ Read the relevant file BEFORE implementing changes in that area:
| Agent Control (AI Gateway native tool-call hook, file-write gating, approval, result capture, run correlation, multi-agent wiring) | `docs/technical/domains/agent-control.md` |
| Agent Control integrator/developer docs (connect an agent, Claude Code + Cursor, generic contract, API ref) | `shared/user-guide-content/content/developers/` |
| AI Trust Index | `docs/technical/domains/ai-trust-index.md` |
| Regulations Tracker (global AI-regulations feed: daily sync, per-country tracking, Horizon/Deadlines/Frameworks, notifications) | `docs/technical/domains/regulations-tracker.md` |
| AI Detection | `docs/technical/domains/ai-detection.md` |
| Risk management | `docs/technical/domains/risk-management.md` |
| Vendors | `docs/technical/domains/vendors.md` |
Expand Down
87 changes: 87 additions & 0 deletions Clients/src/application/config/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,29 @@ const AITrustIndexSettings = lazyRoute(
const AITrustIndexDetail = lazyRoute(
() => import("../../presentation/pages/AITrustIndex/AppDetail"),
);
// ── Regulations Tracker routes ────────────────────────────────────────
const RegulationsTracker = lazyRoute(() => import("../../presentation/pages/RegulationsTracker"));
const RegulationsTrackerBrowse = lazyRoute(
() => import("../../presentation/pages/RegulationsTracker/Browse"),
);
const RegulationsTrackerTracked = lazyRoute(
() => import("../../presentation/pages/RegulationsTracker/Tracked"),
);
const RegulationsTrackerSettings = lazyRoute(
() => import("../../presentation/pages/RegulationsTracker/Settings"),
);
const RegulationsTrackerCountryDetail = lazyRoute(
() => import("../../presentation/pages/RegulationsTracker/CountryDetail"),
);
const RegulationsTrackerHorizon = lazyRoute(
() => import("../../presentation/pages/RegulationsTracker/Horizon"),
);
const RegulationsTrackerDeadlines = lazyRoute(
() => import("../../presentation/pages/RegulationsTracker/Deadlines"),
);
const RegulationsTrackerFrameworks = lazyRoute(
() => import("../../presentation/pages/RegulationsTracker/Frameworks"),
);

const InsightsPage = lazyRoute(() => import("../../presentation/pages/ShadowAI/InsightsPage"));
const UserActivityPage = lazyRoute(
Expand Down Expand Up @@ -788,6 +811,70 @@ export const createRoutes = (
</Suspense>
}
/>
<Route
path="/regulations-tracker"
element={
<Suspense fallback={<LazyFallback />}>
<RegulationsTracker />
</Suspense>
}
/>
<Route
path="/regulations-tracker/browse"
element={
<Suspense fallback={<LazyFallback />}>
<RegulationsTrackerBrowse />
</Suspense>
}
/>
<Route
path="/regulations-tracker/tracked"
element={
<Suspense fallback={<LazyFallback />}>
<RegulationsTrackerTracked />
</Suspense>
}
/>
<Route
path="/regulations-tracker/settings"
element={
<Suspense fallback={<LazyFallback />}>
<RegulationsTrackerSettings />
</Suspense>
}
/>
<Route
path="/regulations-tracker/horizon"
element={
<Suspense fallback={<LazyFallback />}>
<RegulationsTrackerHorizon />
</Suspense>
}
/>
<Route
path="/regulations-tracker/deadlines"
element={
<Suspense fallback={<LazyFallback />}>
<RegulationsTrackerDeadlines />
</Suspense>
}
/>
<Route
path="/regulations-tracker/frameworks"
element={
<Suspense fallback={<LazyFallback />}>
<RegulationsTrackerFrameworks />
</Suspense>
}
/>
<Route
path="/regulations-tracker/:slug"
element={
<Suspense fallback={<LazyFallback />}>
<RegulationsTrackerCountryDetail />
</Suspense>
}
/>
<Route path="/shadow-ai" element={<Navigate to="/shadow-ai/insights" replace />} />
<Route
path="/shadow-ai/insights"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* @fileoverview Regulations Tracker Sidebar Context
*
* Provides the tracked-country count badge to the Regulations Tracker sidebar.
* Follows the same safe-context pattern as AITrustIndexSidebarContext so the
* shared ContextSidebar can read counts without crashing outside the provider.
*
* @module contexts/RegulationsTrackerSidebar.context
*/

import { createContext, useContext, useState, useEffect, useCallback, ReactNode, FC } from "react";
import { getTracked } from "../repository/regulationsTracker.repository";

interface RegulationsTrackerSidebarContextType {
trackedCount: number;
setTrackedCount: (n: number) => void;
refreshTrackedCount: () => void;
}

const RegulationsTrackerSidebarContext = createContext<RegulationsTrackerSidebarContextType | null>(
null,
);

export const RegulationsTrackerSidebarProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [trackedCount, setTrackedCount] = useState(0);

const refreshTrackedCount = useCallback(async () => {
try {
const response = await getTracked();
const rows = Array.isArray(response?.data) ? response.data : [];
setTrackedCount(rows.length);
} catch {
// Sidebar badge is non-critical; ignore failures.
}
}, []);

useEffect(() => {
refreshTrackedCount();
}, [refreshTrackedCount]);

return (
<RegulationsTrackerSidebarContext.Provider
value={{ trackedCount, setTrackedCount, refreshTrackedCount }}
>
{children}
</RegulationsTrackerSidebarContext.Provider>
);
};

export const useRegulationsTrackerSidebarContext = () => {
const context = useContext(RegulationsTrackerSidebarContext);
if (!context) {
throw new Error(
"useRegulationsTrackerSidebarContext must be used within RegulationsTrackerSidebarProvider",
);
}
return context;
};

// Safe version that returns null if not in provider (used by ContextSidebar).
export const useRegulationsTrackerSidebarContextSafe = () => {
return useContext(RegulationsTrackerSidebarContext);
};
7 changes: 7 additions & 0 deletions Clients/src/application/hooks/useActiveModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export function useActiveModule() {
if (pathname.startsWith("/ai-trust-index")) {
return "ai-trust-index";
}
if (pathname.startsWith("/regulations-tracker")) {
return "regulations-tracker";
}
if (pathname.startsWith("/super-admin")) {
return "super-admin";
}
Expand Down Expand Up @@ -76,6 +79,9 @@ export function useActiveModule() {
case "ai-trust-index":
navigate("/ai-trust-index/browse");
break;
case "regulations-tracker":
navigate("/regulations-tracker/browse");
break;
case "super-admin":
navigate("/super-admin");
break;
Expand All @@ -100,6 +106,7 @@ export function useActiveModule() {
"shadow-ai",
"ai-gateway",
"ai-trust-index",
"regulations-tracker",
"super-admin",
].includes(stored)
) {
Expand Down
164 changes: 164 additions & 0 deletions Clients/src/application/hooks/useRegulationsTracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Clients/src/application/hooks/useRegulationsTracker.ts
import { useQuery, useMutation, useQueryClient, keepPreviousData } from "@tanstack/react-query";
import {
getCountries,
getCountryDetail,
getTracked,
trackCountry,
trackBulk,
untrackCountry,
getSettings,
updateSettings,
getHorizon,
getDeadlines,
getFrameworks,
triggerSync,
getImpactAnalysis,
refreshImpactAnalysis,
} from "../repository/regulationsTracker.repository";

const KEY = "regulations-tracker";

// keepPreviousData on the read queries: track/untrack invalidates these keys,
// which triggers a background refetch. Without it, `data` briefly becomes
// undefined and the page's content unmounts and re-mounts — a visible flicker.
// Keeping the previous data holds the UI steady while the fresh data loads.
export function useCountries(filters: { region?: string; q?: string } = {}) {
return useQuery({
queryKey: [KEY, "countries", filters],
queryFn: () => getCountries(filters),
placeholderData: keepPreviousData,
});
}

export function useCountryDetail(slug: string) {
return useQuery({
queryKey: [KEY, "country", slug],
queryFn: () => getCountryDetail(slug),
enabled: !!slug,
placeholderData: keepPreviousData,
});
}

export function useTracked() {
return useQuery({
queryKey: [KEY, "tracked"],
queryFn: () => getTracked(),
placeholderData: keepPreviousData,
});
}

export function useTrackCountry() {
const qc = useQueryClient();
return useMutation({
mutationFn: (slug: string) => trackCountry(slug),
onSuccess: () => {
qc.invalidateQueries({ queryKey: [KEY, "countries"] });
qc.invalidateQueries({ queryKey: [KEY, "tracked"] });
// The detail page reads is_tracked from [KEY, "country", slug]; invalidate the
// "country" prefix so its Track/Untrack button reflects the new state.
qc.invalidateQueries({ queryKey: [KEY, "country"] });
},
});
}

export function useUntrackCountry() {
const qc = useQueryClient();
return useMutation({
mutationFn: (slug: string) => untrackCountry(slug),
onSuccess: () => {
qc.invalidateQueries({ queryKey: [KEY, "countries"] });
qc.invalidateQueries({ queryKey: [KEY, "tracked"] });
qc.invalidateQueries({ queryKey: [KEY, "country"] });
},
});
}

export function useTrackBulk() {
const qc = useQueryClient();
return useMutation({
mutationFn: (slugs: string[]) => trackBulk(slugs),
onSuccess: () => {
qc.invalidateQueries({ queryKey: [KEY, "countries"] });
qc.invalidateQueries({ queryKey: [KEY, "tracked"] });
qc.invalidateQueries({ queryKey: [KEY, "country"] });
},
});
}

export function useSettings() {
// Settings change rarely; a 60s staleTime avoids the global 2s default
// refetching on every interaction.
return useQuery({
queryKey: [KEY, "settings"],
queryFn: () => getSettings(),
staleTime: 60 * 1000,
});
}

export function useUpdateSettings() {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: {
recipient_user_ids: number[];
recipient_emails: string[];
impact_enabled?: boolean;
}) => updateSettings(body),
onSuccess: () => qc.invalidateQueries({ queryKey: [KEY, "settings"] }),
});
}

export function useHorizon() {
return useQuery({
queryKey: [KEY, "horizon"],
queryFn: getHorizon,
placeholderData: keepPreviousData,
});
}

export function useDeadlines() {
return useQuery({
queryKey: [KEY, "deadlines"],
queryFn: getDeadlines,
placeholderData: keepPreviousData,
});
}

export function useFrameworks() {
return useQuery({
queryKey: [KEY, "frameworks"],
queryFn: getFrameworks,
placeholderData: keepPreviousData,
});
}

export function useTriggerSync() {
const qc = useQueryClient();
return useMutation({
mutationFn: triggerSync,
onSuccess: () => {
// Refresh everything the sync may have changed: catalog, tracked list,
// the global feeds, and settings (which carries last-run status).
qc.invalidateQueries({ queryKey: [KEY] });
},
});
}

export function useImpactAnalysis(slug: string) {
return useQuery({
queryKey: [KEY, "impact", slug],
queryFn: () => getImpactAnalysis(slug),
enabled: !!slug,
placeholderData: keepPreviousData,
});
}

export function useRefreshImpactAnalysis() {
const qc = useQueryClient();
return useMutation({
mutationFn: (slug: string) => refreshImpactAnalysis(slug),
onSuccess: (_data, slug) => {
qc.invalidateQueries({ queryKey: [KEY, "impact", slug] });
},
});
}
1 change: 1 addition & 0 deletions Clients/src/application/redux/ui/uiSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type AppModule =
| "shadow-ai"
| "ai-gateway"
| "ai-trust-index"
| "regulations-tracker"
| "super-admin";

const initialState = {
Expand Down
Loading