1616package dev .ikm .komet .kview .klwindows ;
1717
1818import dev .ikm .komet .framework .view .ViewProperties ;
19+ import dev .ikm .komet .framework .window .WindowSettings ;
1920import dev .ikm .komet .layout .area .KlToolArea ;
2021import dev .ikm .komet .layout .preferences .KlPreferencesFactory ;
2122import dev .ikm .komet .layout .preferences .KlProfiles ;
2223import dev .ikm .komet .preferences .KometPreferences ;
24+ import dev .ikm .tinkar .common .service .PluggableService ;
2325import javafx .scene .layout .BorderPane ;
2426import javafx .scene .layout .Pane ;
2527import javafx .scene .layout .Region ;
2830
2931import java .util .UUID ;
3032
33+ import static dev .ikm .komet .kview .events .EventTopics .JOURNAL_TOPIC ;
34+
3135/**
3236 * A non-entity chapter window that hosts a {@link KlToolArea} (e.g. the Claude Assistant)
3337 * inside the Journal workspace.
3438 *
3539 * <p>The window is a thin host: it instantiates the supplied tool-area factory, hands the
3640 * area the journal {@link ViewProperties} and a close callback, and exposes the area's
37- * {@code fxObject()} as its content pane. Tool windows are ephemeral — their conversation
38- * state is not persisted or restored across sessions — so {@link #save()} and
39- * {@link #revert()} are intentionally no-ops.
41+ * {@code fxObject()} as its content pane.
42+ *
43+ * <p><b>Restoration uses the Kl framework, not the entity-centric path.</b> Because a tool
44+ * window has no {@link dev.ikm.tinkar.terms.EntityFacade}, it cannot flow through
45+ * {@link EntityKlWindowFactory} (whose {@code create}/{@code restore} are typed to
46+ * {@link AbstractEntityChapterKlWindow}). Instead {@link #save()} records the hosting
47+ * {@link KlToolArea.Factory} class name, and the static {@link #restore} resolves it across
48+ * module layers with {@link PluggableService#forName(String)} — the same cross-layer
49+ * mechanism the Kl framework uses in {@code KlView.restore}. The journal dispatches to it by
50+ * {@linkplain EntityKlWindowType#getPrefix() window-type prefix}. The area's conversations
51+ * persist independently (per knowledge base, per user), so a restored area reloads its rail.
4052 */
4153public final class ToolAreaChapterKlWindow extends AbstractChapterKlWindow <Pane > {
4254
4355 private static final Logger LOG = LoggerFactory .getLogger (ToolAreaChapterKlWindow .class );
4456
4557 /**
46- * Ephemeral, non -entity window type shared by all tool-area windows. The prefix is
47- * distinct from the entity window types so persisted-workspace scans never mistake a
48- * tool window for an entity chapter.
58+ * Non -entity window type shared by all tool-area windows. The prefix is distinct from the
59+ * entity window types so the journal can dispatch restoration to {@link #restore} and so
60+ * persisted-workspace scans never mistake a tool window for an entity chapter.
4961 */
50- private static final EntityKlWindowType TOOL_WINDOW_TYPE = new EntityKlWindowType () {
62+ public static final EntityKlWindowType TOOL_WINDOW_TYPE = new EntityKlWindowType () {
5163 @ Override
5264 public String getPrefix () {
5365 return "journal_tool_" ;
@@ -59,22 +71,37 @@ public String toString() {
5971 }
6072 };
6173
74+ /** Preference key holding the hosting {@link KlToolArea.Factory} class name. */
75+ public static final String TOOL_AREA_FACTORY_CLASS = "TOOL_AREA_FACTORY_CLASS" ;
76+
77+ static {
78+ // Register the (non-enum) tool window type so EntityKlWindowState.fromPreferences and
79+ // revert() can round-trip the persisted WINDOW_TYPE string. Idempotent.
80+ try {
81+ EntityKlWindowType .Registry .registerInstance (TOOL_WINDOW_TYPE );
82+ } catch (RuntimeException alreadyRegistered ) {
83+ // already registered (duplicate prefix) — fine
84+ }
85+ }
86+
6287 private final UUID windowTopic ;
88+ private final String areaFactoryClassName ;
6389
6490 /**
6591 * Creates a tool window hosting the area produced by {@code toolAreaFactory}.
6692 *
6793 * @param windowTopic unique identifier for this window instance
6894 * @param toolAreaFactory the discovered factory that produces the tool area to host
6995 * @param viewProperties the hosting journal's view properties, passed through to the area
70- * @param preferences window preferences, or {@code null} for an ephemeral window
96+ * @param preferences window preferences node (non- null so the window can be restored)
7197 */
7298 public ToolAreaChapterKlWindow (UUID windowTopic ,
7399 KlToolArea .Factory <?, ?> toolAreaFactory ,
74100 ViewProperties viewProperties ,
75101 KometPreferences preferences ) {
76102 super (viewProperties , preferences );
77103 this .windowTopic = windowTopic ;
104+ this .areaFactoryClassName = toolAreaFactory .getClass ().getName ();
78105
79106 final KlPreferencesFactory areaPreferencesFactory =
80107 KlProfiles .sharedWindowPreferenceFactory (toolAreaFactory .getClass ());
@@ -89,6 +116,41 @@ public ToolAreaChapterKlWindow(UUID windowTopic,
89116 LOG .info ("Created tool window {} hosting {}" , windowTopic , toolAreaFactory .toolName ());
90117 }
91118
119+ /**
120+ * Restores a tool window from its saved preferences using the Kl framework's native,
121+ * cross-layer factory resolution: it reads the persisted {@link KlToolArea.Factory} class
122+ * name and loads it with {@link PluggableService#forName(String)} (which spans the plugin
123+ * module layer), re-creates the area, and re-applies the saved window geometry. No
124+ * {@code EntityFacade} is involved.
125+ *
126+ * @param windowSettings the journal's parent window settings
127+ * @param preferences the saved window preferences node
128+ * @return the restored tool window
129+ */
130+ public static ToolAreaChapterKlWindow restore (WindowSettings windowSettings , KometPreferences preferences ) {
131+ final EntityKlWindowState windowState = EntityKlWindowState .fromPreferences (preferences );
132+ final UUID windowTopic = windowState .getWindowId ();
133+ final UUID journalTopic = preferences .getUuid (JOURNAL_TOPIC )
134+ .orElseThrow (() -> new IllegalStateException ("No journal topic for tool window " + windowTopic ));
135+ final ViewProperties viewProperties =
136+ KlWindowPreferencesUtils .getJournalViewProperties (windowSettings , journalTopic );
137+ final String factoryClassName = preferences .get (TOOL_AREA_FACTORY_CLASS )
138+ .orElseThrow (() -> new IllegalStateException ("No tool-area factory class for " + windowTopic ));
139+ try {
140+ final Class <?> factoryClass = PluggableService .forName (factoryClassName );
141+ final KlToolArea .Factory <?, ?> toolAreaFactory =
142+ (KlToolArea .Factory <?, ?>) factoryClass .getDeclaredConstructor ().newInstance ();
143+ final ToolAreaChapterKlWindow window =
144+ new ToolAreaChapterKlWindow (windowTopic , toolAreaFactory , viewProperties , preferences );
145+ window .revert ();
146+ LOG .info ("Restored tool window {} hosting {}" , windowTopic , factoryClassName );
147+ return window ;
148+ } catch (ReflectiveOperationException e ) {
149+ throw new RuntimeException ("Failed to restore tool window " + windowTopic
150+ + " from factory " + factoryClassName , e );
151+ }
152+ }
153+
92154 @ Override
93155 public UUID getWindowTopic () {
94156 return windowTopic ;
@@ -101,19 +163,29 @@ public EntityKlWindowType getWindowType() {
101163
102164 @ Override
103165 public void onShown () {
104- // No deferred initialization required; the area builds its UI on construction.
166+ // Persist immediately so the journal can restore this window next session
167+ // (the journal's saveWindows() later re-saves the final placement).
168+ save ();
105169 }
106170
107- // ---- Ephemeral: conversation state is not persisted across sessions ---- --------------
171+ // ---- Persistence: window geometry + the hosting tool-area factory class --------------
108172
109173 @ Override
110174 public void save () {
111- // Intentionally no-op: tool windows are not restored across sessions.
175+ super .save (); // base: WINDOW_ID, WINDOW_TYPE, position, size
176+ if (preferences != null && areaFactoryClassName != null ) {
177+ preferences .put (TOOL_AREA_FACTORY_CLASS , areaFactoryClassName );
178+ try {
179+ preferences .flush ();
180+ } catch (Exception e ) {
181+ LOG .error ("Failed to persist tool-area factory class for {}" , windowTopic , e );
182+ }
183+ }
112184 }
113185
114186 @ Override
115187 public void revert () {
116- // Intentionally no-op: nothing to restore.
188+ super . revert (); // base: re-apply persisted position/size
117189 }
118190
119191 // ---- AbstractChapterKlWindow contract (no property panel for tool windows) -----------
@@ -140,11 +212,11 @@ protected void setSelectedPropertyPanel(String selectedPanel) {
140212
141213 @ Override
142214 protected void captureAdditionalState (EntityKlWindowState state ) {
143- // Ephemeral window: no additional state to capture .
215+ // The factory-class key is written directly in save(); no extra state object fields .
144216 }
145217
146218 @ Override
147219 protected void applyAdditionalState (EntityKlWindowState state ) {
148- // Ephemeral window: no additional state to apply.
220+ // No additional state to apply.
149221 }
150222}
0 commit comments