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: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-24 - Accessibility When Hiding Elements Visually Responsively
**Learning:** When using CSS rules like `display: none` to hide text labels within buttons responsively (e.g. shrinking tabs to icon-only on mobile screens), screen readers will also fail to read the inner text, effectively turning them into inaccessible icon-only buttons for visually impaired users.
**Action:** Always ensure that buttons which may have their visual text label hidden dynamically via CSS also have a corresponding dynamic `aria-label` or `title` bound to the parent button element to guarantee persistent accessibility across all screen sizes.
9 changes: 2 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,11 @@ jobs:
- name: Install Playwright (chromium)
run: npx playwright install --with-deps chromium

- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest

- name: Boot Supabase + run two-client Realtime e2e (R1โ€“R7)
run: |
supabase start
npx supabase start
./scripts/run-e2e.sh canvas-sync-regressions.spec.ts --project=chromium --workers=2

- name: Stop Supabase
if: always()
run: supabase stop || true
run: npx supabase stop || true
16 changes: 8 additions & 8 deletions src/components/QuickSortCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ defineProps<{
}>()

const emit = defineEmits<{
(e: 'swipe-right'): void
(e: 'swipe-left'): void
(e: 'swipe-up'): void
(e: 'swipe-down'): void
(e: 'swipeRight'): void
(e: 'swipeLeft'): void
(e: 'swipeUp'): void
(e: 'swipeDown'): void
}>()

const cardRef = ref<HTMLElement | null>(null)
Expand All @@ -105,10 +105,10 @@ const {
haptics: true,
fourDirectional: true,
mouse: true,
onSwipeRight: () => emit('swipe-right'),
onSwipeLeft: () => emit('swipe-left'),
onSwipeUp: () => emit('swipe-up'),
onSwipeDown: () => emit('swipe-down')
onSwipeRight: () => emit('swipeRight'),
onSwipeLeft: () => emit('swipeLeft'),
onSwipeUp: () => emit('swipeUp'),
onSwipeDown: () => emit('swipeDown')
})

// Card transform style - supports both horizontal and vertical movement
Expand Down
28 changes: 21 additions & 7 deletions src/components/ai/AIMemoryHealthDashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,17 @@
</div>

<!-- Error -->
<div v-if="error" class="error-banner glass">{{ error }}</div>
<div v-if="error" class="error-banner glass">
{{ error }}
</div>

<!-- Summary Cards -->
<div v-if="report" class="summary-cards">
<div class="card glass score-card" :class="gradeClass">
<h3>Memory Grade</h3>
<div class="grade-display">{{ report.grade }}</div>
<div class="grade-display">
{{ report.grade }}
</div>
<p>{{ gradeLabel }}</p>
</div>

Expand All @@ -56,21 +60,27 @@

<div class="card glass stat-card">
<h3>Sections</h3>
<div class="stat-value">{{ report.sections.length }}</div>
<div class="stat-value">
{{ report.sections.length }}
</div>
<p>{{ report.mode === 'full' ? 'Full assessment' : 'Quick check' }}</p>
</div>

<div class="card glass stat-card">
<h3>Duration</h3>
<div class="stat-value stat-value--small">{{ formatDuration(report.durationMs) }}</div>
<div class="stat-value stat-value--small">
{{ formatDuration(report.durationMs) }}
</div>
<p>{{ formatTime(report.timestamp) }}</p>
</div>
</div>

<!-- Empty State -->
<div v-else-if="!isRunning" class="empty-state glass">
<p>No assessment results yet.</p>
<p class="empty-hint">"Quick Check" runs heuristic tests instantly. "Full Assessment" adds LLM-as-judge context utilization tests (~30s).</p>
<p class="empty-hint">
"Quick Check" runs heuristic tests instantly. "Full Assessment" adds LLM-as-judge context utilization tests (~30s).
</p>
</div>

<!-- Sections -->
Expand Down Expand Up @@ -115,7 +125,9 @@
<span class="check-name">{{ check.name }}</span>
<span class="check-score" :class="scoreColorClass(check.score)">{{ check.score }}%</span>
</div>
<div class="check-value">{{ check.value }}</div>
<div class="check-value">
{{ check.value }}
</div>
<div v-if="check.recommendation" class="check-recommendation">
{{ check.recommendation }}
</div>
Expand Down Expand Up @@ -160,7 +172,9 @@
<span class="history-time">{{ formatTime(h.timestamp) }}</span>
</div>
</div>
<button class="btn btn-ghost" @click="handleClearHistory">Clear History</button>
<button class="btn btn-ghost" @click="handleClearHistory">
Clear History
</button>
</div>
</div>
</template>
Expand Down
32 changes: 23 additions & 9 deletions src/components/ai/AIQualityDashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<button
class="btn btn-ghost"
:disabled="isRunning"
@click="handleRunTests(1)"
title="Quick check with 1 run per test"
@click="handleRunTests(1)"
>
Quick (1x)
</button>
Expand Down Expand Up @@ -45,7 +45,9 @@
<div v-if="report" class="summary-cards">
<div class="card glass score-card" :class="gradeClass">
<h3>Overall Grade</h3>
<div class="grade-display">{{ report.grade }}</div>
<div class="grade-display">
{{ report.grade }}
</div>
<p>{{ gradeLabel }}</p>
</div>

Expand All @@ -67,14 +69,18 @@

<div class="card glass stat-card">
<h3>Provider</h3>
<div class="stat-value stat-value--small">{{ report.provider }}</div>
<div class="stat-value stat-value--small">
{{ report.provider }}
</div>
<p>{{ formatTime(report.timestamp) }}</p>
</div>
</div>

<!-- Empty State -->
<div v-else-if="!isRunning" class="empty-state glass">
<h3 class="empty-title">AI Quality Assessment</h3>
<h3 class="empty-title">
AI Quality Assessment
</h3>
<p class="empty-desc">
Tests your AI assistant by sending {{ testPrompts.length }} prompts across {{ new Set(testPrompts.map(t => t.category)).size }} categories,
then judges each response against {{ rubrics.length }} quality rubrics using a second LLM call.
Expand Down Expand Up @@ -129,7 +135,7 @@
<span class="result-grade" :class="scoreColorClass(result.overallScore)">
{{ result.grade }}
</span>
<span class="result-grade-dot" :class="gradeDotClass(result.grade)"></span>
<span class="result-grade-dot" :class="gradeDotClass(result.grade)" />
</div>
</div>

Expand All @@ -142,7 +148,9 @@
<!-- AI Response Preview (first 3 lines) -->
<div class="result-response-preview">
<span class="preview-label">AI Response:</span>
<div class="preview-text">{{ result.response }}</div>
<div class="preview-text">
{{ result.response }}
</div>
</div>

<!-- Simple Verdict (derived from judge reasoning) -->
Expand All @@ -154,12 +162,16 @@
<div v-if="expandedCards.has(result.promptId)" class="expanded-content">
<div class="expanded-section">
<h4>Full AI Response</h4>
<div class="response-text">{{ result.response }}</div>
<div class="response-text">
{{ result.response }}
</div>
</div>

<div v-if="result.judgeReasoning" class="expanded-section">
<h4>Judge's Detailed Assessment</h4>
<div class="judge-text">{{ result.judgeReasoning }}</div>
<div class="judge-text">
{{ result.judgeReasoning }}
</div>
</div>

<!-- Expected Behavior -->
Expand Down Expand Up @@ -260,7 +272,9 @@
<span class="history-time">{{ formatTime(h.timestamp) }}</span>
</div>
</div>
<button class="btn btn-ghost" @click="handleClearHistory">Clear History</button>
<button class="btn btn-ghost" @click="handleClearHistory">
Clear History
</button>
</div>
</div>
</template>
Expand Down
28 changes: 20 additions & 8 deletions src/components/ai/AISetupWizard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@

<!-- โ•โ•โ• Step 1: Choose Provider โ•โ•โ• -->
<div v-if="step === 1" class="step-content">
<h2 class="step-title">Set up AI</h2>
<h2 class="step-title">
Set up AI
</h2>
<p class="step-desc">
FlowState uses AI for smart suggestions, task analysis, and chat.
Choose how you'd like to power it.
Expand All @@ -37,7 +39,9 @@
:class="{ selected: chosenProvider === 'groq' }"
@click="chosenProvider = 'groq'"
>
<div class="provider-badge recommended">Recommended</div>
<div class="provider-badge recommended">
Recommended
</div>
<Zap :size="22" class="provider-icon" />
<span class="provider-name">Groq Cloud</span>
<span class="provider-detail">
Expand Down Expand Up @@ -70,7 +74,9 @@

<!-- โ•โ•โ• Step 2a: Groq Setup โ•โ•โ• -->
<div v-if="step === 2 && chosenProvider === 'groq'" class="step-content">
<h2 class="step-title">Connect Groq</h2>
<h2 class="step-title">
Connect Groq
</h2>
<p class="step-desc">
Groq offers free AI API access with generous daily limits.
Create a free account and paste your API key below.
Expand All @@ -90,13 +96,13 @@
<label class="field-label">API Key</label>
<div class="input-row">
<input
:type="showKey ? 'text' : 'password'"
v-model="apiKey"
:type="showKey ? 'text' : 'password'"
placeholder="gsk_..."
class="text-input"
spellcheck="false"
autocomplete="off"
/>
>
<button
class="icon-btn"
:title="showKey ? 'Hide' : 'Show'"
Expand Down Expand Up @@ -133,7 +139,9 @@

<!-- โ•โ•โ• Step 2b: Ollama Setup โ•โ•โ• -->
<div v-if="step === 2 && chosenProvider === 'ollama'" class="step-content">
<h2 class="step-title">Connect Ollama</h2>
<h2 class="step-title">
Connect Ollama
</h2>
<p class="step-desc">
Ollama runs AI models locally on your machine.
Make sure Ollama is installed and running.
Expand All @@ -147,7 +155,9 @@
</div>

<div v-if="!ollamaDetected" class="ollama-steps">
<p class="setup-instruction">To set up Ollama:</p>
<p class="setup-instruction">
To set up Ollama:
</p>
<ol class="setup-list">
<li>
Install from
Expand Down Expand Up @@ -184,7 +194,9 @@
<div class="confirm-icon">
<CheckCircle2 :size="40" />
</div>
<h2 class="step-title">AI is ready!</h2>
<h2 class="step-title">
AI is ready!
</h2>
<p class="step-desc">
<template v-if="chosenProvider === 'groq'">
Groq Cloud is configured. FlowState will use Llama 3.3 70B for AI features.
Expand Down
36 changes: 27 additions & 9 deletions src/components/ai/ChatMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -371,11 +371,11 @@
.filter(taskId => !dismissedCardTaskIds.value.has(taskId))
}

function weeklyPlanPrimaryTaskIds(rec: WeeklyPlanRecommendation): string[] {

Check failure on line 374 in src/components/ai/ChatMessage.vue

View workflow job for this annotation

GitHub Actions / check

'weeklyPlanPrimaryTaskIds' is defined but never used. Allowed unused vars must match /^_/u
return rec.primaryTaskId && !dismissedCardTaskIds.value.has(rec.primaryTaskId) ? [rec.primaryTaskId] : []
}

function weeklyPlanRelatedChipIds(rec: WeeklyPlanRecommendation): string[] {

Check failure on line 378 in src/components/ai/ChatMessage.vue

View workflow job for this annotation

GitHub Actions / check

'weeklyPlanRelatedChipIds' is defined but never used. Allowed unused vars must match /^_/u
return [...new Set(rec.relatedTaskIds ?? [])]
.filter(taskId => taskId !== rec.primaryTaskId && !dismissedCardTaskIds.value.has(taskId))
.slice(0, 4)
Expand Down Expand Up @@ -2586,13 +2586,17 @@
data-testid="ai-clarification"
>
<header class="ai-clarification-header">
<div class="weekly-plan-source">{{ clarification.progressLabel }}</div>
<div class="weekly-plan-source">
{{ clarification.progressLabel }}
</div>
<h2>{{ clarification.locale === 'he' ? 'ืฉืืœื” ืงืฆืจื” ืœืคื ื™ ื”ืชื›ื ื•ืŸ' : 'Quick question before planning' }}</h2>
<p>{{ clarification.summary }}</p>
<details v-if="clarificationDebugLines(clarification).length" class="ai-debug-details">
<summary>{{ clarification.locale === 'he' ? 'ืœืžื” ืื ื™ ืฉื•ืืœ?' : 'Why ask?' }}</summary>
<ul>
<li v-for="line in clarificationDebugLines(clarification)" :key="line">{{ line }}</li>
<li v-for="line in clarificationDebugLines(clarification)" :key="line">
{{ line }}
</li>
</ul>
</details>
</header>
Expand Down Expand Up @@ -2781,7 +2785,9 @@
</div>
<h2>{{ weeklyPlan.headline }}</h2>
<p>{{ weeklyPlan.weekRead.summary }}</p>
<p v-if="weeklyPlan.weekRead.mainTradeoff && !isCompactWeeklyPlan" class="weekly-plan-muted">{{ weeklyPlan.weekRead.mainTradeoff }}</p>
<p v-if="weeklyPlan.weekRead.mainTradeoff && !isCompactWeeklyPlan" class="weekly-plan-muted">
{{ weeklyPlan.weekRead.mainTradeoff }}
</p>
</header>

<section
Expand Down Expand Up @@ -2904,8 +2910,12 @@
<div class="weekly-plan-focus" dir="auto">
{{ weeklyPlan.locale === 'he' ? 'ื ืชื™ื‘' : 'Lane' }}
</div>
<h3 dir="auto">{{ weeklyLaneTitle(rec) }}</h3>
<p dir="auto">{{ weeklyLaneSubtitle(rec) }}</p>
<h3 dir="auto">
{{ weeklyLaneTitle(rec) }}
</h3>
<p dir="auto">
{{ weeklyLaneSubtitle(rec) }}
</p>
</div>
<button
v-if="!props.wideMode"
Expand Down Expand Up @@ -3077,12 +3087,18 @@
>
<div class="weekly-plan-focus" dir="auto">
{{ isCompactWeeklyPlan && weeklyPlan.locale === 'he' ? 'ื ืชื™ื‘' : isCompactWeeklyPlan ? 'Lane' : rec.focusArea }}
<template v-if="isCompactWeeklyPlan">: {{ rec.focusArea }}</template>
<template v-if="isCompactWeeklyPlan">
: {{ rec.focusArea }}
</template>
</div>
<h3>{{ rec.rank }}. {{ rec.title }}</h3>
<p v-if="!isCompactWeeklyPlan">{{ rec.whyThisMatters }}</p>
<p v-if="!isCompactWeeklyPlan">
{{ rec.whyThisMatters }}
</p>
<p>{{ rec.whyThisWeek }}</p>
<p v-if="rec.riskIfIgnored && !isCompactWeeklyPlan" class="weekly-plan-muted">{{ rec.riskIfIgnored }}</p>
<p v-if="rec.riskIfIgnored && !isCompactWeeklyPlan" class="weekly-plan-muted">
{{ rec.riskIfIgnored }}
</p>
<p class="weekly-next-action">
<strong>{{ weeklyPlan.locale === 'he' ? 'ื”ืฆืขื“ ื”ื‘ื' : 'Next action' }}:</strong>
{{ rec.nextAction }}
Expand Down Expand Up @@ -3255,7 +3271,9 @@
<footer v-if="weeklyPlan.deferrals.length && !isCompactWeeklyPlan" class="weekly-plan-footer">
<div v-if="weeklyPlan.deferrals.length">
<strong>{{ weeklyPlan.locale === 'he' ? 'ืœื“ื—ื•ืช ื‘ื›ื•ื•ื ื”' : 'Intentional deferrals' }}</strong>
<p v-for="defer in weeklyPlan.deferrals" :key="defer.taskId">{{ defer.reason }}</p>
<p v-for="defer in weeklyPlan.deferrals" :key="defer.taskId">
{{ defer.reason }}
</p>
</div>
</footer>
</article>
Expand Down Expand Up @@ -3335,7 +3353,7 @@
v-else-if="renderedContent"
class="message-text markdown-body"
:dir="effectiveDirection"
v-html="renderedContent"

Check warning on line 3356 in src/components/ai/ChatMessage.vue

View workflow job for this annotation

GitHub Actions / check

'v-html' directive can lead to XSS attack
/>
<span v-if="inlineFeedbackStatus" class="weekly-question-status inline-feedback-status">
{{ inlineFeedbackStatus }}
Expand Down
3 changes: 2 additions & 1 deletion src/components/base/BaseInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
<div class="input-container">
<slot name="prefix" />

<input :dir="inputDir"
<input
:id="inputId"
ref="inputRef"
v-model="localValue"
:dir="inputDir"
:type="type"
:placeholder="placeholder"
:disabled="disabled"
Expand Down
Loading
Loading