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",
"**/*.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 @@ -185,3 +185,6 @@ XVCJ
Yapily
Zalopay
Zalora
Sublabel
sublabel
dedup
13 changes: 10 additions & 3 deletions .github/workflows/linter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,33 @@ on:
pull_request:
branches: [main]

permissions: {}

jobs:
build:
name: Lint Code Base
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
statuses: write

steps:
- name: Checkout Code
uses: actions/checkout@v5
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
fetch-depth: 0
persist-credentials: false

- 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 }}
LOG_LEVEL: WARN
SHELLCHECK_OPTS: -e SC1091 -e 2086
VALIDATE_ALL_CODEBASE: false
FILTER_REGEX_EXCLUDE: "^(\\.github/|\\.vscode/|code/samples/).*|CODE_OF_CONDUCT.md|CHANGELOG.md"
FILTER_REGEX_EXCLUDE: "^(\\.github/|\\.vscode/|code/samples/).*|(^|/)code/web-client/|CODE_OF_CONDUCT.md|CHANGELOG.md"
VALIDATE_BIOME_FORMAT: false
VALIDATE_PYTHON_BLACK: false
VALIDATE_PYTHON_FLAKE8: false
Expand Down
9 changes: 5 additions & 4 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 as ImportMeta & {env?: Record<string, string | undefined>}).env?.VITE_FLOW;

return (
<div className="app-header">
Expand Down Expand Up @@ -68,12 +68,12 @@ const TabBar = ({
mandateCount: number;
}) => (
<div className="tab-bar">
<button
<button type="button"
className={`tab ${activeTab === 'chat' ? 'active' : ''}`}
onClick={() => onChange('chat')}>
Chat
</button>
<button
<button type="button"
className={`tab ${activeTab === 'mandates' ? 'active' : ''}`}
onClick={() => onChange('mandates')}>
Mandates
Expand Down Expand Up @@ -124,7 +124,7 @@ const ChatInput = ({input, setInput, handleSend, loading}: ChatInputProps) => (
disabled={loading}
className="chat-input"
/>
<button
<button type="button"
onClick={() =>
handleSend({fallbackIfEmpty: DEFAULT_CHAT_STARTER_MESSAGE})
}
Expand All @@ -144,6 +144,7 @@ export default function App() {
const [activeTab, setActiveTab] = useState<TabKey>('chat');
const bottomRef = useRef<HTMLDivElement>(null);

// biome-ignore lint/correctness/useExhaustiveDependencies: scroll to bottom whenever messages change
useEffect(() => {
if (activeTab === 'chat') {
bottomRef.current?.scrollIntoView({behavior: 'smooth'});
Expand Down
7 changes: 4 additions & 3 deletions code/web-client/src/components/InventoryOptionsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function ItemRow({
onClick?: () => void;
}) {
return (
// biome-ignore lint/a11y/useSemanticElements: card contains block content; keyboard handling provided
<div
className={`item-card ${onClick ? 'clickable' : ''} ${selected ? 'selected' : ''}`}
role="button"
Expand All @@ -26,7 +27,7 @@ function ItemRow({
<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 Down Expand Up @@ -65,7 +66,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 @@ -113,7 +114,7 @@ export function InventoryOptionsCard({inventory, onSelect}: Props) {
)}
</div>
{canConfirm && (
<button
<button type="button"
onClick={() => {
setHasConfirmed(true);
onSelect?.(selected);
Expand Down
16 changes: 8 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?.toFixed(2)} (list)
</div>
)}
</>
Expand All @@ -148,7 +148,7 @@ export function MandateApproval({
},
{
label: 'Current',
value: hasCurrentPrice ? `$${current!.toFixed(2)}` : '—',
value: hasCurrentPrice ? `$${current?.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,8 @@ 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 +255,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 +271,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
5 changes: 3 additions & 2 deletions code/web-client/src/components/MandateCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ function JsonBlock({ value }: { value: unknown }) {
function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);
return (
<button
<button type="button"
className="copy-button"
onClick={() => {
navigator.clipboard.writeText(text);
Expand Down Expand Up @@ -267,7 +267,7 @@ export function MandateCard({ entry }: Props) {

return (
<div className="mandate-viewer-card">
<button
<button type="button"
className="card-header"
onClick={() => setExpanded((v) => !v)}
aria-expanded={expanded}>
Expand Down Expand Up @@ -358,6 +358,7 @@ export function MandateCard({ entry }: Props) {
<span>Value</span>
</div>
{sd.disclosures.map((d, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: static list, never reordered
<div key={i} className="disclosure-row">
<span className="mono small">
{truncate(d.salt, 18)}
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 @@ -463,6 +463,7 @@ export function useChat() {
]);

// Auto-poll fallback (15s)
// biome-ignore lint/correctness/useExhaustiveDependencies: intentionally keyed on specific monitoring fields
useEffect(
() => {
if (!isMonitoring || hasPurchaseComplete || !pendingTaskId) return;
Expand Down
5 changes: 4 additions & 1 deletion code/web-client/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ 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 not found');

ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
Expand Down
8 changes: 4 additions & 4 deletions code/web-client/src/utils/mandateEntries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* chronologically ordered list suitable for the Mandates tab.
*/

import type {ChatMessage, MandateChainsFetched, MandateEntry, MandatesSigned, MonitoringStatus, PurchaseComplete, ToolCallArtifact,} from '../types';
import type {ChatMessage, MandateChainsFetched, MandateEntry, MandatesSigned, PurchaseComplete,} from '../types';

type Draft = Omit<MandateEntry, 'id'>;

Expand Down Expand Up @@ -86,9 +86,9 @@ function purchaseEntries(msg: ChatMessage, pc: PurchaseComplete): Draft[] {
return out;
}

function toolCallEntries(msg: ChatMessage, tc: ToolCallArtifact): Draft[] {
// Tool-call artifacts do not produce mandate entries yet.
function toolCallEntries(): Draft[] {
const out: Draft[] = [];
const args = tc.args ?? {};
return out;
}

Expand Down Expand Up @@ -124,7 +124,7 @@ export function deriveMandateEntries(messages: ChatMessage[]): MandateEntry[] {
drafts.push(...purchaseEntries(msg, data as unknown as PurchaseComplete));
break;
case 'tool_call':
drafts.push(...toolCallEntries(msg, data as unknown as ToolCallArtifact));
drafts.push(...toolCallEntries());
break;
case 'mandate_chains_fetched': {
const mcf = data as unknown as MandateChainsFetched;
Expand Down
2 changes: 1 addition & 1 deletion code/web-client/src/utils/productPreviewUnavailable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function normalizeProductPreviewUnavailable(
if (typeof v === 'string') {
const cleaned = v.replace(/[$,]/g, '').trim();
const n = Number(cleaned);
return isNaN(n) ? undefined : n;
return Number.isNaN(n) ? undefined : n;
}
return undefined;
};
Expand Down
Loading
Loading