feat(content): comprehensive extraction hardening, code mode UI, overnight plan

- Relax isBookLikeResponse threshold (>=2 to >=1)
- Widen book patterns: inline prose, verb-preceded, numbered bold, em-dash
- Remove overly aggressive film/song gating on book extraction
- Allow songs to coexist with film/book tags (explicit tags always returned)
- Strengthen TV patterns: seasons, created by, standalone "tv" query match
- Fix TV_EXT_RE to handle both Title|Year|Creator and Title|Creator|Year
- Widen place patterns: type word search in descriptions, bold fallback
- Add "pizza" to isPlaceQuery and preferredFirstTab
- Add Code tab: isCodeQuery, isCodeLikeResponse, extractCodeBlocks, wiring
- Fix image threshold: single image with meaningful alt text shown
- Refactor filterTabsByContext: specialized paths now append remaining content
- Add code mode UI: orange input styling, design system selection, file selection
- DesignSystemGrid: selection toggle only on checkmark, card click opens detail
- Add 64-test extraction quality test suite
- Update overnight plan.md and prompt.md for hardening run

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 10:54:03 +00:00
co-authored by Claude Opus 4.6
parent 75e9274582
commit f0eb4383bd
13 changed files with 1049 additions and 260 deletions
+19 -6
View File
@@ -77,8 +77,13 @@
</div>
<div
class="path-glass-bubble rounded-2xl px-4 py-3 flex items-end gap-2 transition-all duration-300"
:class="focused ? 'border-white/30' : ''"
class="rounded-2xl px-4 py-3 flex items-end gap-2 transition-all duration-300"
:class="[
isCodeMode
? 'bg-accent/15 border border-accent/25 backdrop-blur-xl'
: 'path-glass-bubble',
focused ? (isCodeMode ? 'border-accent/40' : 'border-white/30') : '',
]"
>
<!-- Image attach button -->
<button
@@ -131,10 +136,14 @@
<!-- Send button -->
<button
:disabled="!canSend"
class="shrink-0 path-glass-button path-glass-button-sm rounded-xl px-3 transition-all duration-200"
:class="canSend
? 'hover:opacity-80 active:scale-95'
: 'opacity-30 cursor-not-allowed'"
class="shrink-0 rounded-xl px-3 transition-all duration-200"
:class="[
isCodeMode ? 'bg-accent/80 text-white' : 'path-glass-button path-glass-button-sm',
canSend
? 'hover:opacity-80 active:scale-95'
: 'opacity-30 cursor-not-allowed',
]"
:style="isCodeMode ? 'height: 32px' : ''"
aria-label="Send message"
@click="send"
>
@@ -172,16 +181,20 @@ const props = withDefaults(
disabled?: boolean
streaming?: boolean
placeholder?: string
activeTab?: string
replyTo?: { messageId: string; excerpt: string } | null
}>(),
{
disabled: false,
streaming: false,
placeholder: 'Message AIUI...',
activeTab: '',
replyTo: null,
}
)
const isCodeMode = computed(() => props.activeTab === 'code')
const emit = defineEmits<{
send: [text: string, images: ImageAttachment[]]
extract: [text: string]
@@ -106,7 +106,8 @@
<ChatInput
:disabled="isStreaming || comparison.isAnyStreaming.value"
:streaming="isStreaming || comparison.isAnyStreaming.value"
:placeholder="isStreaming ? 'Waiting for response...' : 'Message AIUI...'"
:placeholder="activeTab === 'code' ? 'Code...' : isStreaming ? 'Waiting for response...' : 'Message AIUI...'"
:active-tab="activeTab"
:reply-to="replyTo"
@send="handleSend"
@extract="handleExtract"
@@ -87,6 +87,7 @@ const promptPairs = computed<PromptPair[]>(() => {
if (content.magazineSections.length > 0) badges.push('Magazine')
if ((content.newsLinks?.length ?? 0) > 0) badges.push('News')
if ((content.websitesLinks?.length ?? 0) > 0) badges.push('Web')
if ((content.codeBlocks?.length ?? 0) > 0) badges.push('Code')
if ((content.apps?.length ?? 0) > 0) badges.push('Apps')
if (content.hasNostr) badges.push('Nostr')
}
@@ -37,12 +37,29 @@
<button
v-for="item in filteredItems"
:key="item.id"
class="text-left p-3 rounded-xl transition-colors group cursor-pointer"
:class="isDark
? 'bg-white/[0.03] hover:bg-white/[0.07]'
: 'bg-black/[0.02] hover:bg-black/[0.05]'"
class="text-left p-3 rounded-xl transition-all duration-150 group relative"
:class="[
codeMode && isDesignTokenSelected(item.id)
? 'ring-2 ring-accent/50 bg-accent/10 cursor-pointer'
: isDark
? 'bg-white/[0.03] hover:bg-white/[0.07] cursor-pointer'
: 'bg-black/[0.02] hover:bg-black/[0.05] cursor-pointer',
]"
@click="selectItem(item)"
>
<!-- Selection toggle (top-right) only this area toggles context selection -->
<div
v-if="codeMode"
class="absolute top-2 right-2 w-5 h-5 rounded-full flex items-center justify-center z-10 cursor-pointer transition-colors"
:class="isDesignTokenSelected(item.id)
? 'bg-accent'
: isDark ? 'bg-white/10 hover:bg-white/20' : 'bg-black/10 hover:bg-black/20'"
@click.stop="toggleDesignToken(item.id)"
>
<svg v-if="isDesignTokenSelected(item.id)" class="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
</svg>
</div>
<!-- Preview swatch for colors -->
<div v-if="item.category === 'colors' && item.preview === 'inline'"
class="h-8 rounded-md mb-2 border"
@@ -92,9 +109,11 @@
import { ref, computed } from 'vue'
import { useTheme } from '@/composables/useTheme'
import { useContentPanel, type DesignSystemItem } from '@/composables/useContentPanel'
import { useCodeContext } from '@/composables/useCodeContext'
const { isDark } = useTheme()
const { openDesignSystemItem } = useContentPanel()
const { codeMode, toggleDesignToken, isDesignTokenSelected } = useCodeContext()
const activeCategory = ref<string>('all')
@@ -3,9 +3,11 @@
<button
class="w-full text-left flex items-center gap-1.5 py-1 px-2 rounded-lg text-xs transition-colors"
:class="[
isActive
? isDark ? 'bg-white/10 text-white/90' : 'bg-black/8 text-gray-900'
: isDark ? 'text-white/60 hover:bg-white/[0.04] hover:text-white/80' : 'text-gray-600 hover:bg-black/[0.03] hover:text-gray-800',
isSelected
? 'bg-accent/15 text-accent ring-1 ring-accent/30'
: isActive
? isDark ? 'bg-white/10 text-white/90' : 'bg-black/8 text-gray-900'
: isDark ? 'text-white/60 hover:bg-white/[0.04] hover:text-white/80' : 'text-gray-600 hover:bg-black/[0.03] hover:text-gray-800',
]"
:style="{ paddingLeft: `${depth * 12 + 8}px` }"
@click="handleClick"
@@ -33,6 +35,9 @@
</svg>
<span class="truncate">{{ entry.name }}</span>
<svg v-if="isSelected" class="w-3 h-3 shrink-0 text-accent ml-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
</svg>
</button>
<!-- Children (when expanded) -->
@@ -52,7 +57,7 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useTheme } from '@/composables/useTheme'
import type { FileEntry } from '@/composables/useCodeContext'
import { useCodeContext, type FileEntry } from '@/composables/useCodeContext'
const props = defineProps<{
entry: FileEntry
@@ -60,6 +65,8 @@ const props = defineProps<{
depth: number
}>()
const { isFileSelected } = useCodeContext()
const emit = defineEmits<{
select: [path: string]
}>()
@@ -68,6 +75,7 @@ const { isDark } = useTheme()
const expanded = ref(props.depth < 1) // Auto-expand first level
const isActive = computed(() => !props.entry.isDirectory && props.activeFile === props.entry.path)
const isSelected = computed(() => !props.entry.isDirectory && isFileSelected(props.entry.path))
function handleClick() {
if (props.entry.isDirectory) {
@@ -172,10 +172,14 @@ const {
activeProject,
fileTree,
activeFile,
codeMode,
selectedFiles,
selectProject: doSelectProject,
openFile,
createProject,
clearActiveFile,
toggleFileSelection,
isFileSelected,
} = useCodeContext()
const search = ref('')
@@ -215,6 +219,7 @@ function backToProjects() {
}
function handleFileSelect(filePath: string) {
toggleFileSelection(filePath)
openFile(filePath)
}