feat(app): Workspace-Umbau Schritt 2 — projectFocus-Spiegel
Neues Singleton services/projectFocus.ts (Publish/Subscribe wie rvs.ts): haelt focusedProjectId, Projekt-Namen und Projekt-Kind. ChatScreen publiziert Focus + Namen EINWEG hinein (rein additive Effekte), damit der kommende Workspace-Canvas den aktiven Kontext und den Code-Projekt-Status kennt, ohne dass ChatScreen den Workspace kennt oder umgebaut wird. Verhaltensneutral, tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ import audioService from '../services/audio';
|
|||||||
import wakeWordService, { loadPassiveListenMs } from '../services/wakeword';
|
import wakeWordService, { loadPassiveListenMs } from '../services/wakeword';
|
||||||
import ProjectsBrowser from '../components/ProjectsBrowser';
|
import ProjectsBrowser from '../components/ProjectsBrowser';
|
||||||
import brainApi, { Project as BrainProject } from '../services/brainApi';
|
import brainApi, { Project as BrainProject } from '../services/brainApi';
|
||||||
|
import projectFocus from '../services/projectFocus';
|
||||||
import phoneCallService from '../services/phoneCall';
|
import phoneCallService from '../services/phoneCall';
|
||||||
import { playWakeReadySound } from '../services/wakeReadySound';
|
import { playWakeReadySound } from '../services/wakeReadySound';
|
||||||
import {
|
import {
|
||||||
@@ -554,6 +555,12 @@ const ChatScreen: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [focusedProjectId]);
|
}, [focusedProjectId]);
|
||||||
|
|
||||||
|
// Focus + Projekt-Namen EINWEG in den projectFocus-Spiegel publizieren,
|
||||||
|
// damit der Workspace-Canvas den aktiven Kontext + Code-Projekt-Status
|
||||||
|
// kennt. Rein additiv — aendert ChatScreens eigenes Verhalten nicht.
|
||||||
|
useEffect(() => { projectFocus.setFocus(focusedProjectId); }, [focusedProjectId]);
|
||||||
|
useEffect(() => { projectFocus.setNames(projectNameById); }, [projectNameById]);
|
||||||
|
|
||||||
// inputText-Spiegel (damit der Draft-Wechsel oben inputText nicht als Dep braucht).
|
// inputText-Spiegel (damit der Draft-Wechsel oben inputText nicht als Dep braucht).
|
||||||
useEffect(() => { inputTextRef.current = inputText; }, [inputText]);
|
useEffect(() => { inputTextRef.current = inputText; }, [inputText]);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* projectFocus — leichter Publish/Subscribe-Spiegel des aktuell fokussierten
|
||||||
|
* Projekt-Kontexts.
|
||||||
|
*
|
||||||
|
* ChatScreen bleibt die Quelle der Wahrheit fuer sein eigenes Rendering und
|
||||||
|
* publiziert hier bei jedem Focus-/Namens-/Kind-Wechsel EINWEG hinein. Der
|
||||||
|
* Workspace-Canvas liest/abonniert das Singleton, um zu wissen welches Projekt
|
||||||
|
* gerade aktiv ist und ob es ein Code-Projekt ist — ohne dass ChatScreen den
|
||||||
|
* Workspace kennen oder umgebaut werden muss.
|
||||||
|
*
|
||||||
|
* Muster wie services/rvs.ts (Singleton mit Listener-Liste + Unsubscribe).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ProjectKind = 'code' | 'chat';
|
||||||
|
|
||||||
|
export interface FocusSnapshot {
|
||||||
|
/** '' = Hauptchat, sonst Projekt-ID */
|
||||||
|
focusedProjectId: string;
|
||||||
|
projectNameById: Record<string, string>;
|
||||||
|
projectKindById: Record<string, ProjectKind>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sub = (snap: FocusSnapshot) => void;
|
||||||
|
|
||||||
|
class ProjectFocus {
|
||||||
|
private snap: FocusSnapshot = {
|
||||||
|
focusedProjectId: '',
|
||||||
|
projectNameById: {},
|
||||||
|
projectKindById: {},
|
||||||
|
};
|
||||||
|
private subs: Sub[] = [];
|
||||||
|
|
||||||
|
// --- Getter (synchron, fuer Nicht-Reaktive Leser) ---
|
||||||
|
|
||||||
|
get(): FocusSnapshot {
|
||||||
|
return this.snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFocusedProjectId(): string {
|
||||||
|
return this.snap.focusedProjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
getProjectName(id: string): string {
|
||||||
|
return this.snap.projectNameById[id] || id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default 'chat' — ein Projekt ist erst 'code' wenn es explizit so
|
||||||
|
* markiert wurde (set_project_kind) oder ein Code-/Desktop-Signal kam. */
|
||||||
|
getProjectKind(id: string): ProjectKind {
|
||||||
|
return this.snap.projectKindById[id] || 'chat';
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Publisher (von ChatScreen aufgerufen) ---
|
||||||
|
|
||||||
|
setFocus(id: string): void {
|
||||||
|
if (this.snap.focusedProjectId === id) return;
|
||||||
|
this.snap = { ...this.snap, focusedProjectId: id };
|
||||||
|
this.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
setNames(map: Record<string, string>): void {
|
||||||
|
// Flacher Merge — behaelt bereits bekannte Namen, ueberschreibt neue.
|
||||||
|
this.snap = {
|
||||||
|
...this.snap,
|
||||||
|
projectNameById: { ...this.snap.projectNameById, ...map },
|
||||||
|
};
|
||||||
|
this.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
setKind(id: string, kind: ProjectKind): void {
|
||||||
|
if (this.snap.projectKindById[id] === kind) return;
|
||||||
|
this.snap = {
|
||||||
|
...this.snap,
|
||||||
|
projectKindById: { ...this.snap.projectKindById, [id]: kind },
|
||||||
|
};
|
||||||
|
this.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
setKinds(map: Record<string, ProjectKind>): void {
|
||||||
|
this.snap = {
|
||||||
|
...this.snap,
|
||||||
|
projectKindById: { ...this.snap.projectKindById, ...map },
|
||||||
|
};
|
||||||
|
this.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Abo ---
|
||||||
|
|
||||||
|
/** Registriert einen Listener und liefert sofort den aktuellen Snapshot. */
|
||||||
|
subscribe(cb: Sub): () => void {
|
||||||
|
this.subs.push(cb);
|
||||||
|
cb(this.snap);
|
||||||
|
return () => {
|
||||||
|
this.subs = this.subs.filter(s => s !== cb);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private emit(): void {
|
||||||
|
const s = this.snap;
|
||||||
|
this.subs.forEach(cb => cb(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectFocus = new ProjectFocus();
|
||||||
|
export default projectFocus;
|
||||||
Reference in New Issue
Block a user