feat: ship v1.0.0 with character graph and writing analytics
Add P7 Cytoscape relationship graph with setting CRUD, P9 cockpit analytics driven by writing_sessions, chapter POV selection, and full test coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,6 +23,7 @@ import { InspirationList } from '@renderer/components/inspiration/InspirationLis
|
||||
import { VersionModal } from '@renderer/components/version/VersionModal'
|
||||
import { LandmarkModal } from '@renderer/components/landmark/LandmarkModal'
|
||||
import { CockpitModal } from '@renderer/components/cockpit/CockpitModal'
|
||||
import { CharacterGraphModal } from '@renderer/components/graph/CharacterGraphModal'
|
||||
import { ChapterBridgeModal } from '@renderer/components/bridge/ChapterBridgeModal'
|
||||
import {
|
||||
editorDirtyAtom,
|
||||
@@ -107,6 +108,7 @@ export function EditorLayout(): React.JSX.Element {
|
||||
const bridgeChapterId = useCockpitStore((s) => s.bridgeChapterId)
|
||||
const clearBridgeChapter = useCockpitStore((s) => s.clearBridgeChapter)
|
||||
const updateChapterLocal = useBookStore((s) => s.updateChapterLocal)
|
||||
const settings = useBookStore((s) => s.settings)
|
||||
|
||||
const dirty = useAtomValue(editorDirtyAtom)
|
||||
const saving = useAtomValue(editorSavingAtom)
|
||||
@@ -120,6 +122,20 @@ export function EditorLayout(): React.JSX.Element {
|
||||
const bookMeta = useBookStore.getState().books.find((b) => b.id === currentBookId)
|
||||
const isChapterTarget = target?.kind === 'chapter'
|
||||
const chapterId = target?.kind === 'chapter' ? target.id : selectedChapterId
|
||||
const currentChapter = isChapterTarget && chapterId ? chapters.find((c) => c.id === chapterId) : null
|
||||
const characterSettings = settings.filter((s) => s.type === 'character')
|
||||
|
||||
const handlePovChange = async (povCharacterId: string | null): Promise<void> => {
|
||||
if (!currentBookId || !chapterId) return
|
||||
const updated = await ipcCall(() =>
|
||||
window.electronAPI.chapter.update({
|
||||
bookId: currentBookId,
|
||||
chapterId,
|
||||
povCharacterId
|
||||
})
|
||||
)
|
||||
updateChapterLocal(updated)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void pomodoroInit()
|
||||
@@ -507,6 +523,21 @@ export function EditorLayout(): React.JSX.Element {
|
||||
)}
|
||||
{isChapterTarget ? (
|
||||
<>
|
||||
<label className="status-pov-label">
|
||||
{t('chapter.pov')}
|
||||
<select
|
||||
data-testid="chapter-pov-select"
|
||||
value={currentChapter?.povCharacterId ?? ''}
|
||||
onChange={(e) => void handlePovChange(e.target.value || null)}
|
||||
>
|
||||
<option value="">{t('analytics.povUnassigned')}</option>
|
||||
{characterSettings.map((c) => (
|
||||
<option key={c.id} value={c.id}>
|
||||
{c.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<span>{t('status.chapter')}: {t('editor.words', { count: wordCount.chapter })}</span>
|
||||
<span>{t('status.volume')}: {t('editor.words', { count: wordCount.volume })}</span>
|
||||
<span>{t('status.book')}: {t('editor.words', { count: wordCount.book })}</span>
|
||||
@@ -532,6 +563,7 @@ export function EditorLayout(): React.JSX.Element {
|
||||
setBridgeOpen(true)
|
||||
}}
|
||||
/>
|
||||
<CharacterGraphModal />
|
||||
<ChapterBridgeModal
|
||||
open={bridgeOpen}
|
||||
chapterId={activeBridgeChapterId}
|
||||
|
||||
Reference in New Issue
Block a user