@littlepartytime/dev-kit 1.8.0 → 1.10.0

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.
@@ -41,7 +41,7 @@ export default function PhoneFrame({ children }: { children: React.ReactNode })
41
41
  <div
42
42
  ref={containerRef}
43
43
  className="flex items-center justify-center h-full overflow-hidden"
44
- style={{ minWidth: 0, minHeight: 0 }}
44
+ style={{ flex: 1, minWidth: 0, minHeight: 0 }}
45
45
  >
46
46
  {/* Wrapper sized to the scaled phone for correct layout flow */}
47
47
  <div style={{ width: BODY_W * scale, height: BODY_H * scale, flexShrink: 0 }}>
@@ -160,10 +160,28 @@ export default function Preview() {
160
160
  }
161
161
  }, [playerCount, playerIndex]);
162
162
 
163
+ // Resizable split panel
164
+ const [splitRatio, setSplitRatio] = useState(0.6); // left column gets 60%
165
+ const panelRef = useRef<HTMLDivElement>(null);
166
+ const dragging = useRef(false);
167
+
168
+ useEffect(() => {
169
+ const onMove = (e: MouseEvent) => {
170
+ if (!dragging.current || !panelRef.current) return;
171
+ const rect = panelRef.current.getBoundingClientRect();
172
+ const ratio = (e.clientX - rect.left) / rect.width;
173
+ setSplitRatio(Math.min(0.8, Math.max(0.2, ratio)));
174
+ };
175
+ const onUp = () => { dragging.current = false; document.body.style.cursor = ''; document.body.style.userSelect = ''; };
176
+ window.addEventListener('mousemove', onMove);
177
+ window.addEventListener('mouseup', onUp);
178
+ return () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
179
+ }, []);
180
+
163
181
  return (
164
182
  <div className="flex gap-4 h-[calc(100vh-80px)]">
165
- {/* Renderer — constrain to phone width so it doesn't hog space */}
166
- <div className="shrink-0 h-full">
183
+ {/* Renderer — half the screen width */}
184
+ <div className="h-full" style={{ width: '50%' }}>
167
185
  <PhoneFrame>
168
186
  {GameRenderer && platform && viewState ? (
169
187
  <GameRenderer platform={platform} state={viewState} />
@@ -175,10 +193,10 @@ export default function Preview() {
175
193
  </PhoneFrame>
176
194
  </div>
177
195
 
178
- {/* Control Panel — fills remaining width, two-column grid */}
179
- <div className="flex-1 min-w-0 grid grid-cols-2 gap-4 overflow-auto">
196
+ {/* Control Panel — fills remaining width, resizable two-column */}
197
+ <div ref={panelRef} className="flex-1 min-w-0 flex h-full">
180
198
  {/* Left column: Players & Controls */}
181
- <div className="flex flex-col gap-4">
199
+ <div className="flex flex-col gap-4 overflow-auto pr-1" style={{ width: `${splitRatio * 100}%` }}>
182
200
  {/* Player Count */}
183
201
  <div className="bg-zinc-900 rounded-lg p-3">
184
202
  <h3 className="text-sm font-bold text-zinc-400 mb-2">Player Count</h3>
@@ -200,9 +218,25 @@ export default function Preview() {
200
218
  const isActive = i === playerIndex;
201
219
  const hue = (i * 137) % 360; // deterministic color per player
202
220
  const playerState = fullState?.players?.find((ps: any) => ps.id === p.id);
203
- const extraEntries = playerState
204
- ? Object.entries(playerState).filter(([k]) => k !== 'id')
205
- : [];
221
+ // Fallback chain: 1) PlayerState field 2) GameState.data mapping table
222
+ const ROLE_KEYS = ['role', 'character', 'team', 'class', 'job', 'faction', 'type'];
223
+ const DATA_MAP_KEYS = ['playerRoles', 'roles', 'playerCharacters', 'characters', 'playerTeams', 'teams'];
224
+ let roleLabel: string | undefined;
225
+ // Try PlayerState direct field
226
+ if (playerState) {
227
+ const entry = Object.entries(playerState).find(([k]) => ROLE_KEYS.includes(k.toLowerCase()));
228
+ if (entry) roleLabel = String(entry[1]);
229
+ }
230
+ // Try GameState.data lookup table
231
+ if (!roleLabel && fullState?.data) {
232
+ for (const mapKey of DATA_MAP_KEYS) {
233
+ const map = fullState.data[mapKey];
234
+ if (map && typeof map === 'object' && !Array.isArray(map)) {
235
+ const val = (map as Record<string, unknown>)[p.id];
236
+ if (val != null) { roleLabel = String(val); break; }
237
+ }
238
+ }
239
+ }
206
240
  return (
207
241
  <button
208
242
  key={p.id}
@@ -227,9 +261,9 @@ export default function Preview() {
227
261
  <span className="text-[10px] text-amber-400 shrink-0">HOST</span>
228
262
  )}
229
263
  </div>
230
- {extraEntries.length > 0 && (
264
+ {roleLabel && (
231
265
  <div className="text-[10px] text-zinc-500 truncate">
232
- {extraEntries.map(([k, v]) => `${k}: ${typeof v === 'object' ? JSON.stringify(v) : String(v)}`).join(' · ')}
266
+ {roleLabel}
233
267
  </div>
234
268
  )}
235
269
  </div>
@@ -268,8 +302,16 @@ export default function Preview() {
268
302
  )}
269
303
  </div>
270
304
 
305
+ {/* Drag handle */}
306
+ <div
307
+ className="shrink-0 w-2 cursor-col-resize flex items-center justify-center group"
308
+ onMouseDown={() => { dragging.current = true; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; }}
309
+ >
310
+ <div className="w-0.5 h-8 bg-zinc-700 rounded group-hover:bg-zinc-500 transition-colors" />
311
+ </div>
312
+
271
313
  {/* Right column: State & Logs */}
272
- <div className="flex flex-col gap-4">
314
+ <div className="flex flex-col gap-4 overflow-auto pl-1" style={{ width: `${(1 - splitRatio) * 100}%` }}>
273
315
  {/* State Editor */}
274
316
  <div className="bg-zinc-900 rounded-lg p-3 flex-1 flex flex-col min-h-0">
275
317
  <h3 className="text-sm font-bold text-zinc-400 mb-2">Game State (Full)</h3>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@littlepartytime/dev-kit",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Development toolkit CLI for Little Party Time game developers",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",