Skip to content
Open
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
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
".gemini/**",
".vscode/**",
".cspell.json",
"**/go.sum"
"**/go.sum",
"docs/assets/*.svg"
],
"dictionaryDefinitions": [
{
Expand Down
3 changes: 3 additions & 0 deletions .cspell/custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ davecgh
dcql
Dcql
DCQL
dedup
deviceauth
Dfile
dmypy
Expand Down Expand Up @@ -164,6 +165,8 @@ spyproject
stablecoins
stdr
stretchr
sublabel
Sublabel
superfences
Truelayer
Trulioo
Expand Down
17 changes: 15 additions & 2 deletions .github/workflows/linter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@ on:
pull_request:
branches: [main]

# Super Linter reads the tree and posts commit statuses; nothing else.
permissions:
contents: read
statuses: write

jobs:
build:
name: Lint Code Base
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v5
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
fetch-depth: 0

- name: Lint Code Base
uses: super-linter/super-linter/slim@v8
uses: super-linter/super-linter/slim@4ce20838b8ab83717e78138c5b3a1407148e0918 # v8.7.0
env:
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -35,6 +40,14 @@ jobs:
MARKDOWN_CONFIG_FILE: ".markdownlint.json"
VALIDATE_MARKDOWN_PRETTIER: false
VALIDATE_JAVASCRIPT_PRETTIER: false
VALIDATE_CSS_PRETTIER: false
VALIDATE_TYPESCRIPT_PRETTIER: false
# The bundled ESLint default config expects the legacy React in scope
# JSX transform and cannot resolve extensionless TypeScript imports,
# so it flags every line of the Vite based web client. Biome and tsc
# via the vite build cover these files.
VALIDATE_TSX: false
VALIDATE_TYPESCRIPT_ES: false
VALIDATE_JSON_PRETTIER: false
VALIDATE_YAML_PRETTIER: false
VALIDATE_GIT_COMMITLINT: false
Expand Down
7 changes: 5 additions & 2 deletions code/web-client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const AppHeader = ({usedServers}: {usedServers: Set<string>}) => {
},
];

const flow = (import.meta as any).env?.VITE_FLOW;
const flow = import.meta.env.VITE_FLOW;

return (
<div className="app-header">
Expand Down Expand Up @@ -69,11 +69,13 @@ const TabBar = ({
}) => (
<div className="tab-bar">
<button
type="button"
className={`tab ${activeTab === 'chat' ? 'active' : ''}`}
onClick={() => onChange('chat')}>
Chat
</button>
<button
type="button"
className={`tab ${activeTab === 'mandates' ? 'active' : ''}`}
onClick={() => onChange('mandates')}>
Mandates
Expand Down Expand Up @@ -125,6 +127,7 @@ const ChatInput = ({input, setInput, handleSend, loading}: ChatInputProps) => (
className="chat-input"
/>
<button
type="button"
onClick={() =>
handleSend({fallbackIfEmpty: DEFAULT_CHAT_STARTER_MESSAGE})
}
Expand All @@ -145,7 +148,7 @@ export default function App() {
const bottomRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (activeTab === 'chat') {
if (activeTab === 'chat' && chatState.messages.length > 0) {
bottomRef.current?.scrollIntoView({behavior: 'smooth'});
}
}, [chatState.messages, activeTab]);
Expand Down
15 changes: 11 additions & 4 deletions code/web-client/src/components/InventoryOptionsCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(52, 211, 153, 0.15);
border: 1px solid rgba(52, 211, 153, 0.3);
background: rgb(52 211 153 / 15%);
border: 1px solid rgb(52 211 153 / 30%);
display: flex;
align-items: center;
justify-content: center;
Expand Down Expand Up @@ -59,7 +59,7 @@
border: none;
padding: 10px 16px;
border-radius: 9px;
font-family: 'Geist', sans-serif;
font-family: Geist, sans-serif;
font-size: 13px;
font-weight: 600;
cursor: pointer;
Expand All @@ -76,6 +76,13 @@
}

.item-card {
// Neutralize user agent button styles; this row is a real <button> for a11y.
appearance: none;
font: inherit;
color: inherit;
text-align: left;
width: 100%;
cursor: default;
padding: 10px 14px;
border-radius: 10px;
transition: all 0.15s;
Expand Down Expand Up @@ -121,7 +128,7 @@

.item-details {
.item-name {
font-family: 'Geist', sans-serif;
font-family: Geist, sans-serif;
font-size: 13px;
font-weight: 500;
color: #e2e8f0;
Expand Down
15 changes: 7 additions & 8 deletions code/web-client/src/components/InventoryOptionsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,14 @@ function ItemRow({
onClick?: () => void;
}) {
return (
<div
<button
type="button"
className={`item-card ${onClick ? 'clickable' : ''} ${selected ? 'selected' : ''}`}
role="button"
tabIndex={0}
onClick={onClick}
onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && onClick?.()}>
onClick={onClick}>
<div className="row-content">
{selected && (
<div className="selected-icon">
<svg width="8" height="8" viewBox="0 0 8 8">
<svg aria-hidden="true" width="8" height="8" viewBox="0 0 8 8">
<path
d="M1.5 4l2 2 3-3"
stroke="white"
Expand All @@ -49,7 +47,7 @@ function ItemRow({
<div className="item-stock">{item.stock} in stock</div>
)}
</div>
</div>
</button>
);
}

Expand All @@ -65,7 +63,7 @@ export function InventoryOptionsCard({inventory, onSelect}: Props) {
<div className="msg-agent inventory-options-container">
<div className="header-wrapper">
<div className="icon-wrapper">
<svg width="10" height="10" viewBox="0 0 10 10">
<svg aria-hidden="true" width="10" height="10" viewBox="0 0 10 10">
<path
d="M2 5l2 2 4-4"
stroke="#34d399"
Expand Down Expand Up @@ -114,6 +112,7 @@ export function InventoryOptionsCard({inventory, onSelect}: Props) {
</div>
{canConfirm && (
<button
type="button"
onClick={() => {
setHasConfirmed(true);
onSelect?.(selected);
Expand Down
19 changes: 11 additions & 8 deletions code/web-client/src/components/MandateApproval.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function MandateApproval({
{/* Header */}
<div className="mandate-header">
<div className="icon-wrapper">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path
d="M8 2L10.5 6.5H14L10.5 9L12 13.5L8 11L4 13.5L5.5 9L2 6.5H5.5L8 2Z"
strokeLinejoin="round"
Expand Down Expand Up @@ -133,7 +133,7 @@ export function MandateApproval({

{hasCurrentPrice && (
<div className="reference-price-note">
Reference price: ${current!.toFixed(2)} (list)
Reference price: ${(current ?? 0).toFixed(2)} (list)
</div>
)}
</>
Expand All @@ -148,7 +148,7 @@ export function MandateApproval({
},
{
label: 'Current',
value: hasCurrentPrice ? `$${current!.toFixed(2)}` : '—',
value: hasCurrentPrice ? `$${(current ?? 0).toFixed(2)}` : '—',
accent: '#f87171',
},
{label: 'Qty', value: String(qty), accent: '#94a3b8'},
Expand Down Expand Up @@ -186,7 +186,7 @@ export function MandateApproval({

{/* Payment method row */}
<div className="fop-row">
<svg width="32" height="20" viewBox="0 0 32 20" fill="none">
<svg aria-hidden="true" width="32" height="20" viewBox="0 0 32 20" fill="none">
<rect
width="32"
height="20"
Expand Down Expand Up @@ -243,8 +243,11 @@ export function MandateApproval({

{state === 'idle' && (
<div className="action-buttons">
<button className="approve-button" onClick={handleSign}>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<button
type="button"
className="approve-button"
onClick={handleSign}>
<svg aria-hidden="true" width="14" height="14" viewBox="0 0 14 14" fill="none">
<path
d="M7 1L8.8 4.8L13 5.3L10 8.2L10.7 12.4L7 10.5L3.3 12.4L4 8.2L1 5.3L5.2 4.8L7 1Z"
stroke="white"
Expand All @@ -255,7 +258,7 @@ export function MandateApproval({
</svg>
Approve & Sign
</button>
<button className="reject-button" onClick={onReject}>
<button type="button" className="reject-button" onClick={onReject}>
Reject
</button>
</div>
Expand All @@ -271,7 +274,7 @@ export function MandateApproval({
{state === 'signed' && (
<div className="signed-state">
<div className="success-badge">
<svg width="10" height="10" viewBox="0 0 10 10">
<svg aria-hidden="true" width="10" height="10" viewBox="0 0 10 10">
<path
d="M2 5l2 2 4-4"
stroke="white"
Expand Down
6 changes: 4 additions & 2 deletions code/web-client/src/components/MandateCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);
return (
<button
type="button"
className="copy-button"
onClick={() => {
navigator.clipboard.writeText(text);
Expand Down Expand Up @@ -268,6 +269,7 @@ export function MandateCard({ entry }: Props) {
return (
<div className="mandate-viewer-card">
<button
type="button"
className="card-header"
onClick={() => setExpanded((v) => !v)}
aria-expanded={expanded}>
Expand Down Expand Up @@ -357,8 +359,8 @@ export function MandateCard({ entry }: Props) {
<span>Key</span>
<span>Value</span>
</div>
{sd.disclosures.map((d, i) => (
<div key={i} className="disclosure-row">
{sd.disclosures.map((d) => (
<div key={d.salt} className="disclosure-row">
<span className="mono small">
{truncate(d.salt, 18)}
</span>
Expand Down
4 changes: 2 additions & 2 deletions code/web-client/src/components/MessageRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export const MessageRenderer = ({
: undefined;
return (
<div className="agent-composite-msg">
{proseText && proseText.trim() && <AgentProse text={proseText} />}
{proseText?.trim() && <AgentProse text={proseText} />}
<ProductPreviewUnavailableCard preview={preview} />
</div>
);
Expand Down Expand Up @@ -183,7 +183,7 @@ export const MessageRenderer = ({

return (
<div className="agent-composite-msg">
{proseText && proseText.trim() && <AgentProse text={proseText} />}
{proseText?.trim() && <AgentProse text={proseText} />}
<MandateApproval
mandate={mandateWithName}
trustedSurface={trustedSurface}
Expand Down
2 changes: 1 addition & 1 deletion code/web-client/src/components/MonitoringCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function MonitoringCard({
</p>
)}
{onCheckNow && (
<button onClick={onCheckNow} className="check-button">
<button type="button" onClick={onCheckNow} className="check-button">
Check now
</button>
)}
Expand Down
2 changes: 1 addition & 1 deletion code/web-client/src/components/ReceiptCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function ReceiptCard({ purchase, itemName }: Props) {
<div className="receipt-card">
<div className="success-header">
<div className="success-badge">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<svg aria-hidden="true" width="18" height="18" viewBox="0 0 18 18" fill="none">
<path
d="M4 9l3.5 3.5 6.5-7"
stroke="white"
Expand Down
2 changes: 1 addition & 1 deletion code/web-client/src/components/UserActionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function UserActionCard({label, sublabel}: Props) {
<div className="user-action-container">
<div className="action-badge">
<div className="check-icon">
<svg width="8" height="8" viewBox="0 0 8 8">
<svg aria-hidden="true" width="8" height="8" viewBox="0 0 8 8">
<path
d="M1.5 4l2 2 3-3"
stroke="white"
Expand Down
7 changes: 4 additions & 3 deletions code/web-client/src/hooks/useChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ export function useChat() {
if (event.status.state === 'failed') {
addMessage({
role: 'system',
text: 'Agent error: ' + JSON.stringify(event.status.message),
text: `Agent error: ${JSON.stringify(event.status.message)}`,
});
}
const statusParts = event.status.message?.parts ?? [];
Expand Down Expand Up @@ -401,12 +401,12 @@ export function useChat() {
}
}
} catch (e) {
addMessage({role: 'system', text: 'Connection error: ' + String(e)});
addMessage({role: 'system', text: `Connection error: ${String(e)}`});
} finally {
setLoading(false);
}
},
[addMessage]);
[addMessage, fetchMandate]);

// Trigger-state polling: 500ms interval while monitoring
const lastTriggerStateRef = useRef<string>('');
Expand Down Expand Up @@ -492,6 +492,7 @@ export function useChat() {
pendingTaskId,
monitoringData?.item_id,
monitoringData?.price_cap,
monitoringData?.qty,
monitoringData?.open_checkout_mandate,
monitoringData?.open_payment_mandate,
sendToAgent,
Expand Down
6 changes: 5 additions & 1 deletion code/web-client/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import ReactDOM from 'react-dom/client';
import App from './App';
import './styles/global.scss';

ReactDOM.createRoot(document.getElementById('root')!).render(
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error('Root element #root is missing from index.html');
}
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
Expand Down
Loading
Loading