feat(w4): ship v1.5.0 timeline, character arc, and compliance

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 17:40:42 +08:00
parent c5c0b7329b
commit cb6b4c3731
51 changed files with 1528 additions and 42 deletions
@@ -1,9 +1,16 @@
import { useMemo } from 'react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useBookStore } from '@renderer/stores/useBookStore'
import { useReferenceStore } from '@renderer/stores/useReferenceStore'
import { useEditStore } from '@renderer/stores/useEditStore'
type RefItem = {
id: string
kind: 'setting' | 'outline' | 'inspiration'
title: string
preview: string
}
export function ReferencePanel(): React.JSX.Element {
const { t } = useTranslation()
const open = useReferenceStore((s) => s.open)
@@ -18,6 +25,7 @@ export function ReferencePanel(): React.JSX.Element {
const outlines = useBookStore((s) => s.outlines)
const settings = useBookStore((s) => s.settings)
const inspirations = useBookStore((s) => s.inspirations)
const [detailItem, setDetailItem] = useState<RefItem | null>(null)
const items = useMemo(() => {
const q = query.trim().toLowerCase()
@@ -90,13 +98,14 @@ export function ReferencePanel(): React.JSX.Element {
{items.map((item) => (
<div
key={item.id}
className="ref-item"
className={`ref-item ${detailItem?.id === item.id ? 'active' : ''}`}
draggable
onDragStart={(e) => {
const plain = item.preview.replace(/<[^>]+>/g, '').slice(0, 200)
e.dataTransfer.setData('text/plain', plain)
}}
onClick={() => void switchTarget({ kind: item.kind, id: item.id })}
onClick={() => setDetailItem(item)}
onDoubleClick={() => void switchTarget({ kind: item.kind, id: item.id })}
role="button"
tabIndex={0}
data-testid={`ref-item-${item.id}`}
@@ -108,6 +117,26 @@ export function ReferencePanel(): React.JSX.Element {
</div>
))}
</div>
{detailItem && (
<div className="ref-detail-drawer" data-testid="reference-detail-drawer">
<div className="ref-detail-header">
<strong>{detailItem.title}</strong>
<button type="button" className="btn" onClick={() => setDetailItem(null)}>
</button>
</div>
<div className="ref-detail-body">
{detailItem.preview.replace(/<[^>]+>/g, '')}
</div>
<button
type="button"
className="btn btn-primary"
onClick={() => void switchTarget({ kind: detailItem.kind, id: detailItem.id })}
>
{t('reference.openInEditor')}
</button>
</div>
)}
</div>
)
}