diff --git a/.bumpversion.toml b/.bumpversion.toml
index 2ed8293..f6ee76a 100644
--- a/.bumpversion.toml
+++ b/.bumpversion.toml
@@ -1,5 +1,5 @@
[tool.bumpversion]
-current_version = "0.2.0"
+current_version = "0.2.1"
commit = true
tag = true
tag_name = "v{new_version}"
diff --git a/VERSION b/VERSION
index 0ea3a94..0c62199 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.2.0
+0.2.1
diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml
index 7d165c6..e60a508 100644
--- a/dev/docker-compose.yml
+++ b/dev/docker-compose.yml
@@ -58,6 +58,7 @@ services:
LANGFLOW__BASE_URL: http://localhost:7860/api/v1
LANGFLOW__API_KEY: ${LANGFLOW_API_KEY:-dev-langflow-api-key}
SECRET_KEY: dev-secret-key-change-in-production
+ CORS_ORIGINS: '["http://localhost:5173", "http://localhost:8000"]'
volumes:
- ../backend:/app
depends_on:
@@ -79,6 +80,8 @@ services:
volumes:
- ../frontend:/app
- /app/node_modules
+ # Comment the following line out if you don't want to mount the local UX package - this requires cloning the UX repository.
+ - ../../ux:/app/node_modules/@tidemark-security/ux
depends_on:
- backend
command: ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
@@ -125,7 +128,7 @@ services:
LANGFLOW_AUTO_LOGIN: "false"
LANGFLOW_SUPERUSER: ${LANGFLOW_SUPERUSER:-admin}
LANGFLOW_SUPERUSER_PASSWORD: ${LANGFLOW_SUPERUSER_PASSWORD:-admin}
- LANGFLOW_SECRET_KEY: ${LANGFLOW_SECRET_KEY:-dev-langflow-secret-change-in-production}
+ LANGFLOW_SECRET_KEY: ${LANGFLOW_SECRET_KEY:-3R1HFctPJZ_MDJg-GQe2Z_TaEyZyXQZtbcCR5l8S0E4=}
LANGFLOW_NEW_USER_IS_ACTIVE: "false"
LANGFLOW_ENABLE_SUPERUSER_CLI: "false"
LANGFLOW_API_KEY_SOURCE: env
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 9f26813..c56d2e0 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "intercept",
- "version": "0.2.0",
+ "version": "0.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "intercept",
- "version": "0.2.0",
+ "version": "0.2.1",
"dependencies": {
"@lexical/rich-text": "^0.35.0",
"@mdxeditor/editor": "^3.53.1",
diff --git a/frontend/package.json b/frontend/package.json
index fcadb68..b688026 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "intercept",
"private": true,
- "version": "0.2.0",
+ "version": "0.2.1",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/components/overlays/ModalShell.tsx b/frontend/src/components/overlays/ModalShell.tsx
index ba91c96..204f3bd 100644
--- a/frontend/src/components/overlays/ModalShell.tsx
+++ b/frontend/src/components/overlays/ModalShell.tsx
@@ -1,26 +1,49 @@
import React from "react";
+import { Dialog } from "@tidemark-security/ux";
+import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
+
import { cn } from "@/utils/cn";
interface ModalShellProps {
children: React.ReactNode;
+ title: string;
+ description?: string;
panelClassName?: string;
contentClassName?: string;
+ onClose?: () => void;
}
export function ModalShell({
children,
+ title,
+ description,
panelClassName,
contentClassName,
+ onClose,
}: ModalShellProps) {
return (
-
-
{
+ if (!open) onClose?.();
+ }}
+ className="p-4"
+ >
+
+
+ {title}
+
+ {description && (
+
+ {description}
+
+ )}
{children}
-
-
+
+
);
}
diff --git a/frontend/src/contexts/SessionProvider.tsx b/frontend/src/contexts/SessionProvider.tsx
index e66f51f..2210688 100644
--- a/frontend/src/contexts/SessionProvider.tsx
+++ b/frontend/src/contexts/SessionProvider.tsx
@@ -163,9 +163,8 @@ export const SessionProvider = ({ children }: SessionProviderProps) => {
dispatch({ type: "START_AUTH" });
try {
if (!browserSupportsPasskeys()) {
- dispatch({ type: "SET_ERROR", payload: "Passkey sign-in is unavailable on this browser." });
dispatch({ type: "SET_UNAUTHENTICATED" });
- return "failed";
+ return "password_required";
}
const begin = await AuthenticationService.beginPasskeyAuthenticationApiV1AuthPasskeysAuthenticateOptionsPost({
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 564e0a2..ebcb136 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -3,148 +3,17 @@
@tailwind utilities;
/**
- * Z-Index Layering System
- *
- * Defines a consistent z-index hierarchy for the application:
- * - Dropdowns and popovers inside modals appear above modal content
- * - Modals obscure page-level dropdowns
- * - Tooltips always appear on top
+ * App-specific styles for Tidemark Intercept.
+ * Shared tokens (CSS variables, animations, bevels) come from
+ * @tidemark-security/ux/tokens.css — imported in main.tsx before index.css.
*/
-:root {
- --z-dropdown: 50;
- --z-sticky: 100;
- --z-modal-backdrop: 1000;
- --z-modal: 1001;
- --z-popover: 1002;
- --z-tooltip: 2000;
-
- --color-default-font: 200 200 200;
- --color-subtext-color: 165 165 165;
- --color-neutral-border: 65 65 65;
- --color-focus-border: 208 255 0;
- --color-default-shadow: 208 255 0;
- --color-scrollbar-thumb: 208 255 0;
- --color-default-background: 10 10 10;
- --color-page-background: 25 25 25;
- --color-brand-secondary: 10 15 13;
- --color-brand-secondary-blush: 5 12 9;
- --color-brand-primary-blush: 208 255 0;
- --color-accent-3-primary-blush: 42 30 92;
- --color-accent-2-primary-blush: 255 0 85;
- --color-accent-1-primary-blush: 0 255 217;
-
- --color-neutral-0: 10 10 10;
- --color-neutral-50: 23 23 23;
- --color-neutral-100: 38 38 38;
- --color-neutral-200: 64 64 64;
- --color-neutral-300: 82 82 82;
- --color-neutral-400: 115 115 115;
- --color-neutral-500: 163 163 163;
- --color-neutral-600: 212 212 212;
- --color-neutral-700: 229 229 229;
- --color-neutral-800: 245 245 245;
- --color-neutral-900: 250 250 250;
- --color-neutral-1000: 255 255 255;
- color-scheme: dark;
-}
-
-:root[data-theme="light"] {
- --color-default-font: 23 23 23;
- --color-subtext-color: 82 82 82;
- --color-neutral-border: 212 212 212;
- --color-focus-border: 10 10 10;
- --color-default-shadow: 10 10 10;
- --color-scrollbar-thumb: 10 10 10;
- --color-default-background: 245 245 245;
- --color-page-background: 30 30 30;
- --color-brand-secondary: 250 250 250;
- --color-brand-secondary-blush: 250 250 250;
-
- --color-neutral-0: 255 255 255;
- --color-neutral-50: 250 250 250;
- --color-neutral-100: 245 245 245;
- --color-neutral-200: 229 229 229;
- --color-neutral-300: 212 212 212;
- --color-neutral-400: 163 163 163;
- --color-neutral-500: 115 115 115;
- --color-neutral-600: 82 82 82;
- --color-neutral-700: 64 64 64;
- --color-neutral-800: 38 38 38;
- --color-neutral-900: 23 23 23;
- --color-neutral-1000: 10 10 10;
- color-scheme: light;
-}
-
-@media (prefers-color-scheme: light) {
- :root:not([data-theme]) {
- --color-default-font: 23 23 23;
- --color-subtext-color: 82 82 82;
- --color-neutral-border: 212 212 212;
- --color-focus-border: 10 10 10;
- --color-default-shadow: 10 10 10;
- --color-scrollbar-thumb: 10 10 10;
- --color-default-background: 255 255 255;
- --color-page-background: 245 245 245;
- --color-brand-secondary: 250 250 250;
- --color-brand-secondary-blush: 250 250 250;
-
- --color-neutral-0: 255 255 255;
- --color-neutral-50: 250 250 250;
- --color-neutral-100: 245 245 245;
- --color-neutral-200: 229 229 229;
- --color-neutral-300: 212 212 212;
- --color-neutral-400: 163 163 163;
- --color-neutral-500: 115 115 115;
- --color-neutral-600: 82 82 82;
- --color-neutral-700: 64 64 64;
- --color-neutral-800: 38 38 38;
- --color-neutral-900: 23 23 23;
- --color-neutral-1000: 10 10 10;
- color-scheme: light;
- }
-}
-
-/**
- * Lucide Icon Sizing Fix
- *
- * Lucide React icons default to 24x24px via inline attributes, but we need them
- * to inherit font-size from parent elements.
- *
- * This makes all Lucide icons use 1em sizing, so they respect text-* classes:
- * - text-body (14px) → 14px icon
- * - text-heading-3 (16px) → 16px icon
- * - text-heading-2 (20px) → 20px icon
- *
- * For icons that need explicit pixel sizes, use the size prop on the Lucide component:
- * - for 24px icon
- * - for 16px icon
- *
- * Note: Tailwind h-* w-* classes do NOT work on Lucide icons because Lucide sets
- * inline width/height attributes. Use the size prop instead.
- */
-svg.lucide {
- width: 1em !important;
- height: 1em !important;
-}
html {
- font-family: "Saira", sans-serif;
- font-size: 16px;
height: 100vh;
display: flex;
flex-direction: column;
}
-/* Page transition animation */
-@keyframes pageEnter {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
-}
-
/* View transitions API for modern browsers */
@view-transition {
navigation: auto;
@@ -168,11 +37,6 @@ html {
to { opacity: 1; }
}
-/* Content transition class - apply to dynamic content that appears/disappears */
-.content-fade-in {
- animation: pageEnter 0.15s ease-out;
-}
-
.prose {
font-size: 14px;
max-width: 1024px;
@@ -182,8 +46,6 @@ body {
display: flex;
flex-direction: column;
flex-grow: 1;
- background-color: rgb(var(--color-default-background));
- color: rgb(var(--color-default-font));
}
#root {
@@ -191,65 +53,6 @@ body {
animation: pageEnter 0.2s ease-out;
}
-@keyframes aiCaretBlink {
- 0%, 45% {
- opacity: 1;
- }
- 50%, 100% {
- opacity: 0;
- }
-}
-
-@keyframes aiScanlineSweep {
- 0% {
- transform: translateX(-115%);
- opacity: 0.35;
- }
- 12% {
- opacity: 1;
- }
- 100% {
- transform: translateX(265%);
- opacity: 0.35;
- }
-}
-
-.ai-caret {
- animation: aiCaretBlink 0.9s steps(1, end) infinite;
-}
-
-.ai-scanline-track {
- position: relative;
- width: 100%;
- height: 10px;
- overflow: hidden;
- background-color: rgb(var(--color-neutral-300));
-}
-
-.ai-scanline {
- position: absolute;
- top: 0;
- left: 0;
- width: 45%;
- height: 100%;
- mask-image: repeating-linear-gradient(
- 135deg,
- transparent,
- transparent 8px,
- black 8px,
- black 16px
- );
- -webkit-mask-image: repeating-linear-gradient(
- 135deg,
- transparent,
- transparent 8px,
- black 8px,
- black 16px
- );
- animation: aiScanlineSweep 4s linear infinite;
-}
-
-
* {
scrollbar-color: rgb(var(--color-scrollbar-thumb)) transparent !important;
}
@@ -260,153 +63,4 @@ body {
::-webkit-scrollbar-thumb {
background-color: rgb(var(--color-scrollbar-thumb));
-}
-
-/* Beveled corner effect using CSS corner-shape */
-.bevel-tr-none {
- corner-top-right-shape: bevel;
- border-top-right-radius: 0px;
-}
-.bevel-tr-sm {
- corner-top-right-shape: bevel;
- border-top-right-radius: 0.125rem; /* 2px */
-}
-.bevel-tr {
- corner-top-right-shape: bevel;
- border-top-right-radius: 0.25rem; /* 4px */
-}
-.bevel-tr-md {
- corner-top-right-shape: bevel;
- border-top-right-radius: 0.375rem; /* 6px */
-}
-.bevel-tr-lg {
- corner-top-right-shape: bevel;
- border-top-right-radius: 0.5rem; /* 8px */
-}
-.bevel-tr-xl {
- corner-top-right-shape: bevel;
- border-top-right-radius: 0.75rem; /* 12px */
-}
-.bevel-tr-2xl {
- corner-top-right-shape: bevel;
- border-top-right-radius: 1rem; /* 16px */
-}
-.bevel-tr-3xl {
- corner-top-right-shape: bevel;
- border-top-right-radius: 1.5rem; /* 24px */
-}
-.bevel-tr-full {
- corner-top-right-shape: bevel;
- border-top-right-radius: 9999px;
-}
-
-.bevel-tl-none {
- corner-top-left-shape: bevel;
- border-top-left-radius: 0px;
-}
-.bevel-tl-sm {
- corner-top-left-shape: bevel;
- border-top-left-radius: 0.125rem; /* 2px */
-}
-.bevel-tl {
- corner-top-left-shape: bevel;
- border-top-left-radius: 0.25rem; /* 4px */
-}
-.bevel-tl-md {
- corner-top-left-shape: bevel;
- border-top-left-radius: 0.375rem; /* 6px */
-}
-.bevel-tl-lg {
- corner-top-left-shape: bevel;
- border-top-left-radius: 0.5rem; /* 8px */
-}
-.bevel-tl-xl {
- corner-top-left-shape: bevel;
- border-top-left-radius: 0.75rem; /* 12px */
-}
-.bevel-tl-2xl {
- corner-top-left-shape: bevel;
- border-top-left-radius: 1rem; /* 16px */
-}
-.bevel-tl-3xl {
- corner-top-left-shape: bevel;
- border-top-left-radius: 1.5rem; /* 24px */
-}
-.bevel-tl-full {
- corner-top-left-shape: bevel;
- border-top-left-radius: 9999px;
-}
-
-.bevel-br-none {
- corner-bottom-right-shape: bevel;
- border-bottom-right-radius: 0px;
-}
-.bevel-br-sm {
- corner-bottom-right-shape: bevel;
- border-bottom-right-radius: 0.125rem; /* 2px */
-}
-.bevel-br {
- corner-bottom-right-shape: bevel;
- border-bottom-right-radius: 0.25rem; /* 4px */
-}
-.bevel-br-md {
- corner-bottom-right-shape: bevel;
- border-bottom-right-radius: 0.375rem; /* 6px */
-}
-.bevel-br-lg {
- corner-bottom-right-shape: bevel;
- border-bottom-right-radius: 0.5rem; /* 8px */
-}
-.bevel-br-xl {
- corner-bottom-right-shape: bevel;
- border-bottom-right-radius: 0.75rem; /* 12px */
-}
-.bevel-br-2xl {
- corner-bottom-right-shape: bevel;
- border-bottom-right-radius: 1rem; /* 16px */
-}
-.bevel-br-3xl {
- corner-bottom-right-shape: bevel;
- border-bottom-right-radius: 1.5rem; /* 24px */
-}
-.bevel-br-full {
- corner-bottom-right-shape: bevel;
- border-bottom-right-radius: 9999px;
-}
-
-.bevel-bl-none {
- corner-bottom-left-shape: bevel;
- border-bottom-left-radius: 0px;
-}
-.bevel-bl-sm {
- corner-bottom-left-shape: bevel;
- border-bottom-left-radius: 0.125rem; /* 2px */
-}
-.bevel-bl {
- corner-bottom-left-shape: bevel;
- border-bottom-left-radius: 0.25rem; /* 4px */
-}
-.bevel-bl-md {
- corner-bottom-left-shape: bevel;
- border-bottom-left-radius: 0.375rem; /* 6px */
-}
-.bevel-bl-lg {
- corner-bottom-left-shape: bevel;
- border-bottom-left-radius: 0.5rem; /* 8px */
-}
-.bevel-bl-xl {
- corner-bottom-left-shape: bevel;
- border-bottom-left-radius: 0.75rem; /* 12px */
-}
-.bevel-bl-2xl {
- corner-bottom-left-shape: bevel;
- border-bottom-left-radius: 1rem; /* 16px */
-}
-.bevel-bl-3xl {
- corner-bottom-left-shape: bevel;
- border-bottom-left-radius: 1.5rem; /* 24px */
-}
-.bevel-bl-full {
- corner-bottom-left-shape: bevel;
- border-bottom-left-radius: 9999px;
}
\ No newline at end of file
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 6f3c34a..cc4a05f 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import App from "./App.tsx";
import "./index.css";
+import "@tidemark-security/ux/tokens.css";
import { ThemeProvider } from "./contexts/ThemeContext";
import { TimezoneProvider } from "./contexts/TimezoneContext";
import { VisualFilterProvider } from "./contexts/VisualFilterContext";
diff --git a/frontend/src/pages/AdminLinkTemplates.tsx b/frontend/src/pages/AdminLinkTemplates.tsx
index fa4b072..da9e0a9 100644
--- a/frontend/src/pages/AdminLinkTemplates.tsx
+++ b/frontend/src/pages/AdminLinkTemplates.tsx
@@ -3,6 +3,7 @@ import { Badge } from "@/components/data-display/Badge";
import { Button } from "@/components/buttons/Button";
import { DropdownMenu } from "@/components/overlays/DropdownMenu";
import { IconButton } from "@/components/buttons/IconButton";
+import { ModalShell } from "@/components/overlays";
import { Table } from "@/components/data-display/Table";
import { TextField } from "@/components/forms/TextField";
import { Toast } from "@/components/feedback/Toast";
@@ -388,9 +389,12 @@ function AdminLinkTemplates() {
{/* Create/Edit Modal */}
{showModal && (
-
-
-
+
{/* Modal Header */}
@@ -597,9 +601,7 @@ function AdminLinkTemplates() {
{isEditing ? "Save Changes" : "Create Template"}
-
-
-
+
)}
{/* Toast Notifications */}
diff --git a/frontend/src/pages/AdminSettings.tsx b/frontend/src/pages/AdminSettings.tsx
index 522ffd6..b6c6a59 100644
--- a/frontend/src/pages/AdminSettings.tsx
+++ b/frontend/src/pages/AdminSettings.tsx
@@ -1110,7 +1110,7 @@ function AdminSettings() {
) : null}
-
+
General
-
+
@@ -1158,7 +1158,7 @@ function AdminSettings() {
System
-
+
@@ -1229,7 +1229,7 @@ function AdminSettings() {
Identity Providers
-
+
@@ -1435,7 +1435,7 @@ function AdminSettings() {
{/* LangFlow Settings Section */}
{/* LangFlow Setup Wizard */}
@@ -1446,7 +1446,7 @@ function AdminSettings() {
-
+
Setup Langflow for Intercept
@@ -1457,6 +1457,7 @@ function AdminSettings() {