@johndimm/constellations 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/App.tsx +360 -66
  2. package/FullPageConstellations.tsx +7 -4
  3. package/components/AppConfirmDialog.tsx +1 -0
  4. package/components/AppHeader.tsx +67 -30
  5. package/components/AppNotifications.tsx +1 -0
  6. package/components/BrowsePeople.tsx +3 -0
  7. package/components/ControlPanel.tsx +229 -250
  8. package/components/Graph.tsx +251 -87
  9. package/components/HelpOverlay.tsx +2 -1
  10. package/components/NodeContextMenu.tsx +123 -3
  11. package/components/PeopleBrowserSidebar.tsx +15 -6
  12. package/components/Sidebar.tsx +46 -19
  13. package/components/TimelineView.tsx +1 -0
  14. package/hooks/useExpansion.ts +85 -230
  15. package/hooks/useGraphActions.ts +1 -0
  16. package/hooks/useGraphState.ts +75 -40
  17. package/hooks/useKioskMode.ts +1 -0
  18. package/hooks/useNodeClickHandler.ts +23 -15
  19. package/hooks/useSearchHandlers.ts +60 -21
  20. package/host.ts +1 -1
  21. package/index.css +17 -3
  22. package/index.tsx +5 -3
  23. package/package.json +4 -2
  24. package/services/aiService.ts +27 -0
  25. package/services/aiUtils.ts +285 -195
  26. package/services/cacheService.ts +1 -0
  27. package/services/crossrefService.ts +1 -0
  28. package/services/deepseekService.ts +479 -0
  29. package/services/geminiService.ts +543 -736
  30. package/services/graphUtils.ts +128 -18
  31. package/services/imageService.ts +18 -0
  32. package/services/openAlexService.ts +1 -0
  33. package/services/resolveImageForTitle.ts +458 -0
  34. package/services/wikipediaImage.ts +1 -0
  35. package/services/wikipediaService.ts +79 -49
  36. package/sessionHandoff.ts +26 -0
  37. package/types.ts +3 -0
  38. package/utils/evidenceUtils.ts +1 -0
  39. package/utils/graphLogicUtils.ts +1 -0
  40. package/utils/wikiUtils.ts +14 -2
@@ -1,70 +1,107 @@
1
+ "use client";
1
2
  import React from 'react';
2
- import { ChevronRight, ChevronLeft, Key } from 'lucide-react';
3
+ import { ChevronRight, ChevronLeft, X } from 'lucide-react';
3
4
  import { GraphNode } from '../types';
4
5
 
5
6
  interface AppHeaderProps {
6
7
  showHeader: boolean;
7
8
  panelCollapsed: boolean;
8
9
  setPanelCollapsed: React.Dispatch<React.SetStateAction<boolean>>;
9
- showBrowse: boolean;
10
- handleOpenPeopleBrowser: () => void;
11
10
  selectedNode: GraphNode | null;
12
11
  sidebarCollapsed: boolean;
13
- setSidebarCollapsed: React.Dispatch<React.SetStateAction<boolean>>;
14
12
  setSidebarToggleSignal: React.Dispatch<React.SetStateAction<number>>;
15
- onReset: () => void;
13
+ /** When set, shows a top-right control that leaves full-screen (e.g. back to player). */
14
+ onClose?: () => void;
15
+ /**
16
+ * When set (e.g. `/` or `/player`), the close control is a real `href` link so navigation works
17
+ * even if pointer-event layering blocked the old button. `onClick` can still run for cleanup.
18
+ */
19
+ closeHref?: string;
20
+ /**
21
+ * When the host app shows its own top bar (e.g. Trailer Vision nav, ~44px), set so this header
22
+ * does not sit at viewport top:0 and steal clicks from the host nav. Use `top-11` for 2.75rem.
23
+ */
24
+ offsetTopClass?: string;
16
25
  }
17
26
 
18
27
  const AppHeader: React.FC<AppHeaderProps> = ({
19
28
  showHeader,
20
29
  panelCollapsed,
21
30
  setPanelCollapsed,
22
- showBrowse,
23
- handleOpenPeopleBrowser,
24
31
  selectedNode,
25
32
  sidebarCollapsed,
26
- setSidebarCollapsed,
27
33
  setSidebarToggleSignal,
28
- onReset
34
+ onClose,
35
+ closeHref,
36
+ offsetTopClass = "top-0",
29
37
  }) => {
30
38
  if (!showHeader) return null;
31
39
 
32
40
  return (
33
- <header className="fixed top-0 left-0 right-0 z-50 min-h-14 bg-slate-900/95 backdrop-blur border-b border-slate-800 flex items-center justify-between px-2 sm:px-3 py-2 gap-2 overflow-x-hidden max-w-full">
34
- <div className="flex items-center gap-1.5 sm:gap-2 min-w-0">
41
+ <header
42
+ className={`absolute left-0 right-0 z-[200] h-14 max-h-14 min-h-14 shrink-0 pointer-events-auto bg-slate-900/95 backdrop-blur flex items-center justify-between px-2 sm:px-3 py-2 gap-2 overflow-x-hidden max-w-full ${offsetTopClass}`}
43
+ >
44
+ <div className="pointer-events-auto flex min-w-0 items-center gap-1.5 sm:gap-2">
35
45
  <button
46
+ type="button"
36
47
  onClick={() => setPanelCollapsed(c => !c)}
37
48
  className="w-9 h-9 sm:w-10 sm:h-10 bg-slate-800/80 border border-slate-700 rounded-lg flex items-center justify-center text-slate-300 hover:text-white transition flex-shrink-0"
38
- title={panelCollapsed ? "Show controls" : "Hide controls"}
49
+ title={
50
+ panelCollapsed
51
+ ? "Show left panel — search, save/load, graph options"
52
+ : "Hide left panel"
53
+ }
54
+ aria-label={panelCollapsed ? "Show control panel" : "Hide control panel"}
39
55
  >
40
56
  {panelCollapsed ? <ChevronRight size={18} /> : <ChevronLeft size={18} />}
41
57
  </button>
42
- <button
43
- onClick={(e) => {
44
- e.preventDefault();
45
- window.location.href = window.location.origin + window.location.pathname;
46
- }}
47
- className="text-base sm:text-lg font-bold text-red-500 whitespace-nowrap hover:text-red-400 transition-colors"
48
- >
49
- Constellations
50
- </button>
58
+ {closeHref ? (
59
+ <a
60
+ href={closeHref}
61
+ title="Film & Music — return to hub"
62
+ className="text-base sm:text-lg font-bold text-red-500 whitespace-nowrap hover:text-red-400 transition-colors"
63
+ >
64
+ Constellations
65
+ </a>
66
+ ) : (
67
+ <span className="text-base sm:text-lg font-bold text-red-500 whitespace-nowrap">
68
+ Constellations
69
+ </span>
70
+ )}
51
71
  </div>
52
- <div className="flex items-center gap-3 sm:gap-4 flex-shrink-0 mr-2">
53
- <button
54
- onClick={handleOpenPeopleBrowser}
55
- className={`text-sm font-bold uppercase tracking-widest transition-colors ${showBrowse ? 'text-red-500' : 'text-slate-400 hover:text-white'}`}
56
- >
57
- People
58
- </button>
72
+ <div className="flex items-center gap-2 sm:gap-3 flex-shrink-0 mr-1">
59
73
  {selectedNode && (
60
74
  <button
61
- onClick={() => { setSidebarCollapsed(c => !c); setSidebarToggleSignal(s => s + 1); }}
75
+ type="button"
76
+ onClick={() => {
77
+ setSidebarToggleSignal((s) => s + 1);
78
+ }}
62
79
  className="w-9 h-9 sm:w-10 sm:h-10 bg-slate-800/80 border border-slate-700 rounded-lg flex items-center justify-center text-slate-300 hover:text-white transition flex-shrink-0"
63
- title="Toggle details"
80
+ title={
81
+ sidebarCollapsed
82
+ ? "Show right details (selected node on graph)"
83
+ : "Hide right details"
84
+ }
85
+ aria-label={sidebarCollapsed ? "Show details panel" : "Hide details panel"}
64
86
  >
65
87
  {sidebarCollapsed ? <ChevronLeft size={18} /> : <ChevronRight size={18} />}
66
88
  </button>
67
89
  )}
90
+ {!closeHref && onClose && (
91
+ <button
92
+ type="button"
93
+ onClick={(e) => {
94
+ e.preventDefault();
95
+ e.stopPropagation();
96
+ onClose();
97
+ }}
98
+ className="w-9 h-9 sm:w-10 sm:h-10 bg-slate-800/80 border border-slate-700 rounded-lg flex items-center justify-center text-slate-300 hover:text-white hover:border-slate-600 transition flex-shrink-0"
99
+ title="Leave full screen and return to Trailer Vision"
100
+ aria-label="Close constellations"
101
+ >
102
+ <X size={20} strokeWidth={2} />
103
+ </button>
104
+ )}
68
105
  </div>
69
106
  </header>
70
107
  );
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import React from 'react';
2
3
 
3
4
  interface AppNotificationsProps {
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import React, { useState, useEffect, useCallback } from 'react';
2
3
  import { Search, ChevronLeft, ChevronRight, X, Filter } from 'lucide-react';
3
4
 
@@ -124,6 +125,8 @@ const BrowsePeople: React.FC<BrowsePeopleProps> = ({ baseUrl = '', onSelect, exp
124
125
  'South African', 'New Zealander', 'Israeli', 'Saudi Arabian', 'Korean', 'Thai', 'Vietnamese', 'Indonesian'
125
126
  ];
126
127
 
128
+ const isPureBrowse = useCallback(() => !searchTerm.trim() && !occupation && !nationality, [searchTerm, occupation, nationality]);
129
+
127
130
  const buildSearchQuery = useCallback(() => {
128
131
  const parts: string[] = [];
129
132