@weppy/roblox-mcp 2.6.4 → 2.7.1
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +24 -0
- package/README.md +22 -11
- package/install.ps1 +85 -19
- package/install.sh +60 -15
- package/llms-full.txt +1 -2
- package/llms.txt +4 -3
- package/package.json +1 -1
- package/plugins/weppy-roblox-mcp/.claude-plugin/plugin.json +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ChangelogDetailPage-DYQthxhC.js → ChangelogDetailPage-DxktONbL.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ChangelogPage-CNxAGfwG.css +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ChangelogPage-DOY-SmWM.js +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ConfirmModal-CM3ElkBC.js → ConfirmModal-CmJCz5rg.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ConnectionPage-DdKcJZt6.js → ConnectionPage-BiiIQs7f.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{GameChangeDetail-DGphCFbI.js → GameChangeDetail-D1kE2L_v.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{InfoLabel-CMSu30c4.js → InfoLabel-B9KT3kfU.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{OverviewPage-DM74adCU.js → OverviewPage-T2WCTHpo.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/PlaytestPage-CW8Xfp6b.js +11 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SettingsPage-Cc9Wnj8M.css +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SettingsPage-DcUt5qjG.js +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{StatusBadge-BhRLY0yK.js → StatusBadge-Bxe88ki2.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{SyncPage-Col3nhBp.js → SyncPage-C09YF6D5.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/Tabs-876h0_zB.css +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/Tabs-BNtOqYlc.js +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/TierComparison-Dyf-knpe.js +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ToolsPage-B3fMk-22.css +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ToolsPage-HKbmsKMQ.js +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{TooltipText-Bnvm1FcC.js → TooltipText-Baw38jOU.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/UiStudioPage-Cbgl7xIC.js +16 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/UiStudioPage-DQOd5VrU.css +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{WhatsNewPage-M1EeHSH-.js → WhatsNewPage-BlOSWgSn.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-FfT2oWAB.js +343 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-Gu7et1DX.css +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/sample-requests-Bx1zpYdG.css +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/sample-requests-X_hsWdLX.js +1 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ui-studio-sample-DrNTD6yi.png +0 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{useLiveUptime-Dc2iJiFi.js → useLiveUptime-DhJz484L.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/index.html +2 -2
- package/plugins/weppy-roblox-mcp/dist/index.js +450 -85
- package/plugins/weppy-roblox-mcp/roblox-plugin/WeppyRobloxMCP.rbxm +0 -0
- package/.gitattributes +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -60
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -30
- package/.github/ISSUE_TEMPLATE/install_help.yml +0 -48
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -16
- package/.github/workflows/install-test.yml +0 -140
- package/Dockerfile +0 -13
- package/docs/assets/screenshots/antigravity/antigravity_mcp_raw.png +0 -0
- package/docs/assets/screenshots/antigravity/antigravity_mcp_services_menu.png +0 -0
- package/docs/assets/screenshots/antigravity/antigravity_raw_config_menu.png +0 -0
- package/docs/assets/screenshots/dashboard/dashboard_changelog1.png +0 -0
- package/docs/assets/screenshots/dashboard/dashboard_changelog2.png +0 -0
- package/docs/assets/screenshots/dashboard/dashboard_connection.png +0 -0
- package/docs/assets/screenshots/dashboard/dashboard_overview.png +0 -0
- package/docs/assets/screenshots/dashboard/dashboard_playtest.png +0 -0
- package/docs/assets/screenshots/dashboard/dashboard_sync.png +0 -0
- package/docs/assets/screenshots/dashboard/dashboard_tools.png +0 -0
- package/docs/assets/screenshots/plugin/connection/connection-guide.png +0 -0
- package/docs/assets/screenshots/plugin/installation/main-screen.png +0 -0
- package/docs/assets/screenshots/plugin/installation/plugins-menu.png +0 -0
- package/docs/assets/screenshots/plugin/installation/settings-screen.png +0 -0
- package/docs/assets/screenshots/plugin/installation/toolbar-button.png +0 -0
- package/docs/assets/screenshots/plugin/license/dashboard-license-screen.png +0 -0
- package/docs/assets/screenshots/plugin/license/plugin-license-screen.png +0 -0
- package/docs/assets/screenshots/plugin/sync/sync-conflict.png +0 -0
- package/docs/assets/screenshots/plugin/sync/sync-main.png +0 -0
- package/docs/assets/screenshots/plugin/sync/sync-overview.png +0 -0
- package/docs/assets/screenshots/roblox-explorer/roblox-explorer-property-window.png +0 -0
- package/docs/assets/screenshots/roblox-explorer/roblox-explorer-screen.png +0 -0
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ChangelogPage-BH87M2hn.css +0 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ChangelogPage-WFqQ5h8e.js +0 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/PlaytestPage-Dq3kV_rX.js +0 -11
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SettingsPage-DTv0NbEY.css +0 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SettingsPage-QQd8aPdd.js +0 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/TierComparison-CYfIb0tr.js +0 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ToolsPage-54vIMfZg.css +0 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/ToolsPage-Ba_6GnUZ.js +0 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-B-nqZCE3.js +0 -380
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-COXUWuq2.css +0 -1
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Weppy Roblox MCP — MCP server that lets AI coding agents control a live Roblox Studio session with 21 tools, 140+ actions, bidirectional sync, automated playtest, and multi-place support",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.7.1"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "weppy-roblox-mcp",
|
|
14
14
|
"source": "./plugins/weppy-roblox-mcp",
|
|
15
15
|
"description": "Weppy Roblox MCP — MCP server that lets AI coding agents control a live Roblox Studio session with 21 tools, 140+ actions, bidirectional sync, automated playtest, and multi-place support",
|
|
16
|
-
"version": "2.
|
|
16
|
+
"version": "2.7.1",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "hope1026"
|
|
19
19
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
## [2.7.1] - 2026-05-05
|
|
7
|
+
|
|
8
|
+
### Improvements
|
|
9
|
+
|
|
10
|
+
- **Stability hardening** — Internal reliability improvements across the MCP server and Roblox Studio plugin. No behavior changes for existing workflows.
|
|
11
|
+
|
|
12
|
+
## [2.7.0] - 2026-05-05
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- **UI Studio launch** — A new workflow for iterating on Roblox Studio UI design together with an AI agent. From the UI Studio page in the dashboard, you can now use:
|
|
17
|
+
- **History tab** — Compare before/after snapshots and design suggestions for each request, side by side.
|
|
18
|
+
- **Analysis tab** — Snapshot comparison modal with batch selection and deletion.
|
|
19
|
+
- **Design Check** — Reviews your UI against design quality criteria and surfaces concrete improvements.
|
|
20
|
+
|
|
21
|
+
### Improvements
|
|
22
|
+
|
|
23
|
+
- **More accurate property types when syncing local edits back to Studio** — When you edit synced files locally and push the changes back to Studio, the server now resolves property types via class-aware metadata instead of guessing from value shape. Edits to enum, color, or vector properties on specific instances are no longer silently saved as the wrong type.
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
- **Claude Code detection on Windows** — On Windows, Claude Code could be mis-detected and even a fresh install would fail with a confusing "already exists" error. Detection is now correct, and an existing valid registration is treated as success.
|
|
28
|
+
|
|
29
|
+
- **Dashboard tooltips no longer clipped** — Tooltips inside scrollable areas (Settings, UI Studio history) were being cut off. They now display in full regardless of the surrounding container.
|
|
6
30
|
|
|
7
31
|
|
|
8
32
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **WEPPY** is an MCP server that lets AI coding agents control a live Roblox Studio session - create and edit scripts, instances, terrain, lighting, assets, audio, and animations through natural language.
|
|
4
4
|
|
|
5
|
-
**Action-based tool surface · Bidirectional sync · Automated playtest · Multi-place support**
|
|
5
|
+
**Action-based tool surface · Bidirectional sync · Automated playtest · UI Studio · Multi-place support**
|
|
6
6
|
|
|
7
7
|
**English** | [한국어](https://weppyai.com/ko) | [日本語](https://weppyai.com/ja) | [Español](https://weppyai.com/es) | [Português](https://weppyai.com/pt-br) | [Bahasa Indonesia](https://weppyai.com/id) | [Deutsch](https://weppyai.com/de)
|
|
8
8
|
|
|
@@ -87,7 +87,7 @@ AI works from a synchronized local mirror, so multi-file updates stay consistent
|
|
|
87
87
|
- Basic: one-way sync (Studio -> Local)
|
|
88
88
|
- Pro: bidirectional sync + per-type Direction/Apply Mode + history + multi-place
|
|
89
89
|
|
|
90
|
-

|
|
90
|
+

|
|
91
91
|
|
|
92
92
|
### 3) Playtest: Let AI run and verify tests automatically
|
|
93
93
|
|
|
@@ -97,19 +97,29 @@ AI can control Roblox Studio playtests directly. It can start and stop Play (F5)
|
|
|
97
97
|
- "Write a test that verifies the SpawnLocation is above the ground and run it."
|
|
98
98
|
- "Validate that the script I just changed runs without errors in playtest."
|
|
99
99
|
|
|
100
|
-

|
|
100
|
+

|
|
101
101
|
|
|
102
|
-
### 4)
|
|
102
|
+
### 4) UI Studio: Build and inspect in-game UI
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
UI Studio lets AI agents create in-game UI that matches your game's style, or analyze the UI you already have and suggest improvements.
|
|
105
|
+
|
|
106
|
+
- Clarify the UI goal with guided questions about purpose, screen, target devices, and visual direction
|
|
107
|
+
- Create or refine game-style menus, HUDs, buttons, labels, image panels, and other Roblox UI elements directly in Studio
|
|
108
|
+
- Capture the result, compare before/after changes, and follow dashboard suggestions for layout, readability, touch targets, and safe areas
|
|
109
|
+
|
|
110
|
+

|
|
111
|
+
|
|
112
|
+
### 5) WEPPY Dashboard: Monitor AI work in real time
|
|
113
|
+
|
|
114
|
+
The MCP server provides a web dashboard where you can check connection status, tool execution history, sync state, UI Studio history, and game change logs in real time.
|
|
105
115
|
|
|
106
116
|
- Server/Plugin/Agent connection status at a glance
|
|
107
117
|
- Compare every change the AI made via Before & After in Changelog
|
|
108
|
-
- Analyze workflow with tool execution history and statistics
|
|
118
|
+
- Analyze workflow with tool execution history, UI Studio captures, and statistics
|
|
109
119
|
|
|
110
|
-

|
|
120
|
+

|
|
111
121
|
|
|
112
|
-
###
|
|
122
|
+
### 6) WEPPY Roblox Explorer: Browse Studio hierarchy in VSCode
|
|
113
123
|
|
|
114
124
|
View the full instance tree of your Roblox Studio place directly inside VSCode. Navigate services, open synced scripts and property files, and track sync status - all without switching to Studio.
|
|
115
125
|
WEPPY Roblox Explorer is a companion VSCode extension for sync data generated by WEPPY. Tree browsing works from synced files, and live sync state or direction indicators are enhanced when the local MCP server is running.
|
|
@@ -119,13 +129,14 @@ Install from [VS Code Marketplace](https://marketplace.visualstudio.com/items?it
|
|
|
119
129
|
- Click to open synced scripts and property files
|
|
120
130
|
- Multi-place support with sync status indicators
|
|
121
131
|
|
|
122
|
-

|
|
132
|
+

|
|
123
133
|
|
|
124
134
|
## Use Cases
|
|
125
135
|
|
|
126
136
|
- **Rapid prototyping**: Describe a game mechanic in natural language and watch AI build it in Studio
|
|
127
137
|
- **Bulk refactoring**: Rename a module interface and update every dependent script in one request
|
|
128
138
|
- **Terrain & environment**: Generate procedural terrain, set lighting/atmosphere, place assets - all from a single prompt
|
|
139
|
+
- **UI design**: Generate in-game UI, capture previews, and iterate on Design Check suggestions
|
|
129
140
|
- **Multi-file consistency**: AI reads the full project via Sync and applies changes across related scripts together
|
|
130
141
|
- **Asset integration**: Search the Creator Store, insert models, and configure properties without leaving your editor
|
|
131
142
|
|
|
@@ -161,7 +172,7 @@ Yes. Any MCP-compatible AI client works.
|
|
|
161
172
|
Yes. AI can create instances, write scripts, generate terrain, set up lighting, insert assets, configure physics, and more - all inside a live Roblox Studio session. It goes beyond code generation to executable actions.
|
|
162
173
|
|
|
163
174
|
### What is the difference between Basic and Pro?
|
|
164
|
-
Basic (Free) includes MCP tool execution and one-way sync (Studio -> Local). Pro adds bidirectional sync, bulk operations, terrain generation, spatial analysis, audio/animation control, and multi-place support. See the Pro upgrade page.
|
|
175
|
+
Basic (Free) includes MCP tool execution and one-way sync (Studio -> Local). Pro adds bidirectional sync, UI Studio, bulk operations, terrain generation, spatial analysis, audio/animation control, and multi-place support. See the Pro upgrade page.
|
|
165
176
|
|
|
166
177
|
### How is Weppy different from other Roblox MCP servers?
|
|
167
178
|
Weppy uses action-based dispatching instead of separate tools for each function. This reduces AI token consumption significantly. It also provides bidirectional project sync and multi-place support, which most alternatives lack.
|
|
@@ -171,7 +182,7 @@ The server runs on localhost only (127.0.0.1:3002). Forbidden paths (CoreGui, Co
|
|
|
171
182
|
|
|
172
183
|
## Pro Upgrade
|
|
173
184
|
|
|
174
|
-
Bidirectional Sync, advanced build capabilities, and AI token efficiency - all in one upgrade.
|
|
185
|
+
Bidirectional Sync, UI Studio, advanced build capabilities, and AI token efficiency - all in one upgrade.
|
|
175
186
|
|
|
176
187
|
[Pro Upgrade Guide](https://weppyai.com/en/plans)
|
|
177
188
|
|
package/install.ps1
CHANGED
|
@@ -112,7 +112,18 @@ function Test-McpJsonConfigured($configPath) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
#
|
|
115
|
+
# Require an explicit `@<tag>` so the installer can upgrade legacy bare
|
|
116
|
+
# entries (`@weppy/roblox-mcp`) — those reuse npx cache and trap users on
|
|
117
|
+
# outdated versions. Tagged entries (`@latest`, `@2.6.4`, …) are preserved.
|
|
118
|
+
function Test-WeppyPackageSpec($value) {
|
|
119
|
+
if ([string]::IsNullOrWhiteSpace($value)) {
|
|
120
|
+
return $false
|
|
121
|
+
}
|
|
122
|
+
return $value -match '^@weppy/roblox-mcp@.+$'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Add the MCP server under the canonical `mcpServers` wrapper in the Antigravity
|
|
126
|
+
# config and strip any legacy flat key left over from earlier versions.
|
|
116
127
|
function Add-AntigravityMcpConfig($configPath) {
|
|
117
128
|
$parentDir = Split-Path $configPath -Parent
|
|
118
129
|
if (-not (Test-Path $parentDir)) { New-Item -ItemType Directory -Path $parentDir -Force | Out-Null }
|
|
@@ -134,7 +145,7 @@ const next = { ...config };
|
|
|
134
145
|
delete next['weppy-roblox-mcp'];
|
|
135
146
|
next.mcpServers = {
|
|
136
147
|
...(mcpServers || {}),
|
|
137
|
-
'weppy-roblox-mcp': { command: 'npx', args: ['-y', '@weppy/roblox-mcp'] }
|
|
148
|
+
'weppy-roblox-mcp': { command: 'npx', args: ['-y', '@weppy/roblox-mcp@latest'] }
|
|
138
149
|
};
|
|
139
150
|
config = next;
|
|
140
151
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
@@ -153,7 +164,8 @@ function Test-AntigravityMcpConfigured($configPath) {
|
|
|
153
164
|
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
|
|
154
165
|
$hasLegacyFlatKey = $config.PSObject.Properties.Name -contains 'weppy-roblox-mcp'
|
|
155
166
|
$server = $config.mcpServers.'weppy-roblox-mcp'
|
|
156
|
-
|
|
167
|
+
# Accept args[1] as the weppy package regardless of whether a version tag is appended.
|
|
168
|
+
$hasCanonicalArgs = ($server.args -is [System.Array]) -and ($server.args.Count -eq 2) -and ($server.args[0] -eq '-y') -and (Test-WeppyPackageSpec $server.args[1])
|
|
157
169
|
return ($server.command -eq 'npx') -and $hasCanonicalArgs -and (-not $hasLegacyFlatKey)
|
|
158
170
|
}
|
|
159
171
|
catch {
|
|
@@ -174,7 +186,8 @@ const fs = require('fs');
|
|
|
174
186
|
const configPath = process.env.MCP_CODEX_CONFIG_PATH;
|
|
175
187
|
const serverName = 'weppy-roblox-mcp';
|
|
176
188
|
const expectedCommand = 'npx';
|
|
177
|
-
|
|
189
|
+
// Require an explicit `@<tag>` so the installer can upgrade legacy bare entries.
|
|
190
|
+
const packageSpecPattern = /^@weppy\/roblox-mcp@.+$/;
|
|
178
191
|
const headerPattern = new RegExp(
|
|
179
192
|
'^\\s*\\[\\s*mcp_servers\\.' + serverName.replace(/[.*+?^${}()|[\]\\\\]/g, '\\$&') + '\\s*\\]\\s*(?:#.*)?$'
|
|
180
193
|
);
|
|
@@ -568,8 +581,10 @@ try {
|
|
|
568
581
|
!parsed.hasConflict &&
|
|
569
582
|
parsed.command === expectedCommand &&
|
|
570
583
|
Array.isArray(parsed.args) &&
|
|
571
|
-
parsed.args.length ===
|
|
572
|
-
parsed.args
|
|
584
|
+
parsed.args.length === 2 &&
|
|
585
|
+
parsed.args[0] === '-y' &&
|
|
586
|
+
typeof parsed.args[1] === 'string' &&
|
|
587
|
+
packageSpecPattern.test(parsed.args[1]);
|
|
573
588
|
|
|
574
589
|
process.exit(isConfigured ? 0 : 1);
|
|
575
590
|
} catch {
|
|
@@ -595,7 +610,7 @@ const configPath = process.env.MCP_CONFIG_PATH;
|
|
|
595
610
|
let config = {};
|
|
596
611
|
try { config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch {}
|
|
597
612
|
if (!config.mcpServers) config.mcpServers = {};
|
|
598
|
-
config.mcpServers['weppy-roblox-mcp'] = { command: 'npx', args: ['-y', '@weppy/roblox-mcp'] };
|
|
613
|
+
config.mcpServers['weppy-roblox-mcp'] = { command: 'npx', args: ['-y', '@weppy/roblox-mcp@latest'] };
|
|
599
614
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
600
615
|
"@
|
|
601
616
|
} finally {
|
|
@@ -627,7 +642,7 @@ catch {
|
|
|
627
642
|
# ═══════════════════════════════════
|
|
628
643
|
Write-Step "1/2" "Setup Roblox Studio Plugin"
|
|
629
644
|
|
|
630
|
-
if (Confirm-Action " Run npx -y @weppy/roblox-mcp --setup?") {
|
|
645
|
+
if (Confirm-Action " Run npx -y @weppy/roblox-mcp@latest --setup?") {
|
|
631
646
|
try {
|
|
632
647
|
$npmCommandPath = Resolve-NpmCommand
|
|
633
648
|
$npmDir = Split-Path $npmCommandPath -Parent
|
|
@@ -641,9 +656,11 @@ if (Confirm-Action " Run npx -y @weppy/roblox-mcp --setup?") {
|
|
|
641
656
|
try {
|
|
642
657
|
New-Item -ItemType Directory -Path $setupWorkingDir -Force | Out-Null
|
|
643
658
|
Set-Location $setupWorkingDir
|
|
644
|
-
# stdin
|
|
645
|
-
# pwsh
|
|
646
|
-
|
|
659
|
+
# Isolate stdin with an empty pipe so the stdio MCP server does not inherit
|
|
660
|
+
# the interactive pwsh terminal stdin (which would cause it to hang under irm|iex).
|
|
661
|
+
# The @latest tag forces npx to resolve from the registry instead of
|
|
662
|
+
# reusing an older version pinned in the npm cache.
|
|
663
|
+
$null | & $npxPath -y "@weppy/roblox-mcp@latest" --setup
|
|
647
664
|
if ($LASTEXITCODE -ne 0) {
|
|
648
665
|
Write-Warn "Setup encountered a warning (non-blocking)"
|
|
649
666
|
} else {
|
|
@@ -675,9 +692,30 @@ $notDetected = @()
|
|
|
675
692
|
|
|
676
693
|
$claudeProjectConfig = Join-Path (Get-Location).Path '.mcp.json'
|
|
677
694
|
$claudeGlobalConfig = Join-Path $env:USERPROFILE '.claude\mcp.json'
|
|
678
|
-
$claudeCodeConfigured = (Test-McpJsonConfigured $claudeProjectConfig) -or (Test-McpJsonConfigured $claudeGlobalConfig)
|
|
679
695
|
$claudeCodeCliCommand = Resolve-OptionalCliCommand 'claude'
|
|
680
696
|
|
|
697
|
+
# `claude mcp add` stores entries under ~/.claude.json or in local/user scope,
|
|
698
|
+
# so prefer `claude mcp list` as the source of truth when the CLI is available
|
|
699
|
+
# (the JSON path checks remain as a fallback). The entry counts as configured
|
|
700
|
+
# only when its args carry an explicit `@<tag>` — legacy bare entries fall
|
|
701
|
+
# through and get re-registered with the canonical `@latest` form.
|
|
702
|
+
function Test-ClaudeCliConfigured($cliCommand) {
|
|
703
|
+
if (-not $cliCommand) { return $false }
|
|
704
|
+
try {
|
|
705
|
+
$listOutput = & $cliCommand mcp list 2>$null
|
|
706
|
+
if ($LASTEXITCODE -ne 0) { return $false }
|
|
707
|
+
$line = $listOutput | Select-String -Pattern '^weppy-roblox-mcp:' | Select-Object -First 1
|
|
708
|
+
if (-not $line) { return $false }
|
|
709
|
+
return ($line.Line -match '@weppy/roblox-mcp@')
|
|
710
|
+
} catch {
|
|
711
|
+
return $false
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
$claudeCodeConfigured = (Test-ClaudeCliConfigured $claudeCodeCliCommand) `
|
|
716
|
+
-or (Test-McpJsonConfigured $claudeProjectConfig) `
|
|
717
|
+
-or (Test-McpJsonConfigured $claudeGlobalConfig)
|
|
718
|
+
|
|
681
719
|
if ($claudeCodeConfigured) {
|
|
682
720
|
$detectedNames += 'Claude Code (configured)'
|
|
683
721
|
$detectedTypes += 'claude-code'
|
|
@@ -762,7 +800,7 @@ else {
|
|
|
762
800
|
|
|
763
801
|
if ($detectedNames.Count -eq 0) {
|
|
764
802
|
Write-Warn "No AI apps detected"
|
|
765
|
-
Write-Info "Register MCP server manually: npx -y @weppy/roblox-mcp"
|
|
803
|
+
Write-Info "Register MCP server manually: npx -y @weppy/roblox-mcp@latest"
|
|
766
804
|
}
|
|
767
805
|
else {
|
|
768
806
|
Write-Host ""
|
|
@@ -816,12 +854,40 @@ else {
|
|
|
816
854
|
Write-Ok "Already configured: $appName"
|
|
817
855
|
}
|
|
818
856
|
elseif ($claudeCodeCliCommand) {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
#
|
|
822
|
-
|
|
857
|
+
$claudeStderrFile = Join-Path ([System.IO.Path]::GetTempPath()) ("weppy-claude-{0}.err" -f ([System.Guid]::NewGuid().ToString("N")))
|
|
858
|
+
try {
|
|
859
|
+
# Best-effort remove any legacy bare entry so the subsequent add can
|
|
860
|
+
# install the canonical `@latest` form.
|
|
861
|
+
try { & $claudeCodeCliCommand mcp remove weppy-roblox-mcp *> $null } catch {}
|
|
862
|
+
& $claudeCodeCliCommand mcp add weppy-roblox-mcp -- npx -y "@weppy/roblox-mcp@latest" 2> $claudeStderrFile
|
|
863
|
+
$claudeExit = $LASTEXITCODE
|
|
864
|
+
if ($claudeExit -eq 0) {
|
|
865
|
+
Write-Ok "Registered: $appName"
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
$stderrContent = if (Test-Path $claudeStderrFile) { Get-Content $claudeStderrFile -Raw } else { '' }
|
|
869
|
+
if ($stderrContent -match '(?i)already exists') {
|
|
870
|
+
# Already registered in another scope — not a failure
|
|
871
|
+
Write-Ok "Already configured: $appName"
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
Write-Fail "Failed: $appName (exit=$claudeExit)"
|
|
875
|
+
Write-Host " CLI: $claudeCodeCliCommand" -ForegroundColor DarkGray
|
|
876
|
+
if ($stderrContent) {
|
|
877
|
+
Write-Host " stderr:" -ForegroundColor DarkGray
|
|
878
|
+
$stderrContent.TrimEnd("`r","`n").Split("`n") | ForEach-Object {
|
|
879
|
+
Write-Host " $($_.TrimEnd())" -ForegroundColor DarkGray
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
# Fall back to writing the JSON config directly when the CLI fails for other reasons
|
|
883
|
+
Add-McpToConfig $claudeGlobalConfig
|
|
884
|
+
Write-Ok "Registered via fallback JSON: $appName"
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
finally {
|
|
889
|
+
Remove-Item $claudeStderrFile -ErrorAction SilentlyContinue
|
|
823
890
|
}
|
|
824
|
-
Write-Ok "Registered: $appName"
|
|
825
891
|
}
|
|
826
892
|
else {
|
|
827
893
|
Write-Fail "Failed: $appName (CLI/config unavailable)"
|
|
@@ -842,7 +908,7 @@ else {
|
|
|
842
908
|
}
|
|
843
909
|
elseif ($codexCliCommand) {
|
|
844
910
|
try { & $codexCliCommand mcp remove weppy-roblox-mcp *> $null } catch {}
|
|
845
|
-
& $codexCliCommand mcp add weppy-roblox-mcp -- npx -y "@weppy/roblox-mcp"
|
|
911
|
+
& $codexCliCommand mcp add weppy-roblox-mcp -- npx -y "@weppy/roblox-mcp@latest"
|
|
846
912
|
if ($LASTEXITCODE -ne 0) {
|
|
847
913
|
throw 'codex mcp add failed'
|
|
848
914
|
}
|
package/install.sh
CHANGED
|
@@ -98,7 +98,7 @@ const configPath = process.env.MCP_CONFIG_PATH;
|
|
|
98
98
|
let config = {};
|
|
99
99
|
try { config = JSON.parse(fs.readFileSync(configPath, "utf8")); } catch {}
|
|
100
100
|
if (!config.mcpServers) config.mcpServers = {};
|
|
101
|
-
config.mcpServers["weppy-roblox-mcp"] = { command: "npx", args: ["-y", "@weppy/roblox-mcp"] };
|
|
101
|
+
config.mcpServers["weppy-roblox-mcp"] = { command: "npx", args: ["-y", "@weppy/roblox-mcp@latest"] };
|
|
102
102
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
103
103
|
'
|
|
104
104
|
}
|
|
@@ -142,7 +142,7 @@ const next = { ...config };
|
|
|
142
142
|
delete next["weppy-roblox-mcp"];
|
|
143
143
|
next.mcpServers = {
|
|
144
144
|
...(mcpServers || {}),
|
|
145
|
-
"weppy-roblox-mcp": { command: "npx", args: ["-y", "@weppy/roblox-mcp"] }
|
|
145
|
+
"weppy-roblox-mcp": { command: "npx", args: ["-y", "@weppy/roblox-mcp@latest"] }
|
|
146
146
|
};
|
|
147
147
|
config = next;
|
|
148
148
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
@@ -161,13 +161,17 @@ function isJsonObject(value) {
|
|
|
161
161
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
162
162
|
}
|
|
163
163
|
function hasExpectedCommandShape(value) {
|
|
164
|
+
// Require an explicit `@<tag>` so the installer can upgrade legacy bare
|
|
165
|
+
// entries (`@weppy/roblox-mcp`) — those reuse npx cache and trap users on
|
|
166
|
+
// outdated versions. Tagged entries (`@latest`, `@2.6.4`, …) are preserved.
|
|
164
167
|
return (
|
|
165
168
|
isJsonObject(value) &&
|
|
166
169
|
value.command === "npx" &&
|
|
167
170
|
Array.isArray(value.args) &&
|
|
168
171
|
value.args.length === 2 &&
|
|
169
172
|
value.args[0] === "-y" &&
|
|
170
|
-
value.args[1] === "
|
|
173
|
+
typeof value.args[1] === "string" &&
|
|
174
|
+
/^@weppy\/roblox-mcp@.+$/.test(value.args[1])
|
|
171
175
|
);
|
|
172
176
|
}
|
|
173
177
|
try {
|
|
@@ -191,7 +195,8 @@ const fs = require("fs");
|
|
|
191
195
|
const configPath = process.env.MCP_CODEX_CONFIG_PATH;
|
|
192
196
|
const serverName = "weppy-roblox-mcp";
|
|
193
197
|
const expectedCommand = "npx";
|
|
194
|
-
|
|
198
|
+
// Require an explicit `@<tag>` so the installer can upgrade legacy bare entries.
|
|
199
|
+
const packageSpecPattern = /^@weppy\/roblox-mcp@.+$/;
|
|
195
200
|
const headerPattern = new RegExp(
|
|
196
201
|
"^\\s*\\[\\s*mcp_servers\\." + serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s*\\]\\s*(?:#.*)?$",
|
|
197
202
|
);
|
|
@@ -585,8 +590,10 @@ try {
|
|
|
585
590
|
!parsed.hasConflict &&
|
|
586
591
|
parsed.command === expectedCommand &&
|
|
587
592
|
Array.isArray(parsed.args) &&
|
|
588
|
-
parsed.args.length ===
|
|
589
|
-
parsed.args
|
|
593
|
+
parsed.args.length === 2 &&
|
|
594
|
+
parsed.args[0] === "-y" &&
|
|
595
|
+
typeof parsed.args[1] === "string" &&
|
|
596
|
+
packageSpecPattern.test(parsed.args[1]);
|
|
590
597
|
|
|
591
598
|
process.exit(isConfigured ? 0 : 1);
|
|
592
599
|
} catch {
|
|
@@ -640,7 +647,7 @@ success "Node.js $(node -v) detected"
|
|
|
640
647
|
# ═══════════════════════════════════
|
|
641
648
|
step "1/2" "Setup Roblox Studio Plugin"
|
|
642
649
|
|
|
643
|
-
if confirm " Run npx -y @weppy/roblox-mcp --setup?"; then
|
|
650
|
+
if confirm " Run npx -y @weppy/roblox-mcp@latest --setup?"; then
|
|
644
651
|
setup_tmp_dir=""
|
|
645
652
|
if setup_tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/weppy-setup-XXXXXX" 2>/dev/null); then
|
|
646
653
|
:
|
|
@@ -651,7 +658,9 @@ if confirm " Run npx -y @weppy/roblox-mcp --setup?"; then
|
|
|
651
658
|
if [ -n "${setup_tmp_dir:-}" ] && [ -d "$setup_tmp_dir" ]; then
|
|
652
659
|
# stdin을 /dev/null로 격리: curl|bash 파이프 모드에서 stdio MCP 서버가
|
|
653
660
|
# bash의 남은 스크립트 바이트를 소비해버리는 문제를 방지한다
|
|
654
|
-
|
|
661
|
+
# The @latest tag forces npx to resolve from the registry instead of
|
|
662
|
+
# reusing an older version pinned in the npm cache.
|
|
663
|
+
if (cd "$setup_tmp_dir" && npx -y "@weppy/roblox-mcp@latest" --setup </dev/null); then
|
|
655
664
|
success "Setup complete"
|
|
656
665
|
else
|
|
657
666
|
warn "Setup encountered a warning (non-blocking)"
|
|
@@ -671,7 +680,7 @@ fi
|
|
|
671
680
|
step "2/2" "Register MCP with AI apps"
|
|
672
681
|
printf " Automatic registration: Claude Code, Claude Desktop, Cursor, Codex CLI/App, Gemini CLI, Antigravity\n"
|
|
673
682
|
|
|
674
|
-
MCP_COMMAND='npx -y @weppy/roblox-mcp'
|
|
683
|
+
MCP_COMMAND='npx -y @weppy/roblox-mcp@latest'
|
|
675
684
|
|
|
676
685
|
# AI app detection
|
|
677
686
|
declare -a DETECTED_NAMES=()
|
|
@@ -679,11 +688,25 @@ declare -a DETECTED_TYPES=()
|
|
|
679
688
|
declare -a NOT_DETECTED=()
|
|
680
689
|
|
|
681
690
|
# Claude Code
|
|
691
|
+
# `claude mcp add` stores entries under ~/.claude.json or in local/user scope,
|
|
692
|
+
# so prefer `claude mcp list` as the source of truth when the CLI is available
|
|
693
|
+
# (the JSON path checks remain as a fallback).
|
|
682
694
|
CLAUDE_PROJECT_MCP_CONFIG="$PWD/.mcp.json"
|
|
683
695
|
CLAUDE_GLOBAL_MCP_CONFIG="$HOME/.claude/mcp.json"
|
|
684
696
|
CLAUDE_CLI_COMMAND="$(resolve_optional_cli_command claude 2>/dev/null || true)"
|
|
685
697
|
|
|
686
|
-
|
|
698
|
+
is_claude_cli_configured() {
|
|
699
|
+
[ -n "$CLAUDE_CLI_COMMAND" ] || return 1
|
|
700
|
+
# The entry counts as configured only when its args carry an explicit `@tag`
|
|
701
|
+
# (e.g. `@latest`). Legacy bare entries fall through and get re-registered.
|
|
702
|
+
"$CLAUDE_CLI_COMMAND" mcp list 2>/dev/null \
|
|
703
|
+
| grep "^weppy-roblox-mcp:" \
|
|
704
|
+
| grep -q "@weppy/roblox-mcp@"
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if is_claude_cli_configured \
|
|
708
|
+
|| is_json_mcp_configured "$CLAUDE_PROJECT_MCP_CONFIG" \
|
|
709
|
+
|| is_json_mcp_configured "$CLAUDE_GLOBAL_MCP_CONFIG"; then
|
|
687
710
|
DETECTED_NAMES+=("Claude Code (configured)")
|
|
688
711
|
DETECTED_TYPES+=("claude-code")
|
|
689
712
|
elif [ -n "$CLAUDE_CLI_COMMAND" ]; then
|
|
@@ -816,12 +839,34 @@ else
|
|
|
816
839
|
|
|
817
840
|
case "$app_type" in
|
|
818
841
|
claude-code)
|
|
819
|
-
if
|
|
842
|
+
if is_claude_cli_configured \
|
|
843
|
+
|| is_json_mcp_configured "$CLAUDE_PROJECT_MCP_CONFIG" \
|
|
844
|
+
|| is_json_mcp_configured "$CLAUDE_GLOBAL_MCP_CONFIG"; then
|
|
820
845
|
success "Already configured: $app_name"
|
|
821
|
-
elif [ -n "$CLAUDE_CLI_COMMAND" ]
|
|
822
|
-
|
|
846
|
+
elif [ -n "$CLAUDE_CLI_COMMAND" ]; then
|
|
847
|
+
claude_stderr_file=$(mktemp "${TMPDIR:-/tmp}/weppy-claude-XXXXXX.err" 2>/dev/null || echo "${HOME}/weppy-claude.err")
|
|
848
|
+
# Best-effort remove any legacy bare entry so the subsequent add can
|
|
849
|
+
# install the canonical `@latest` form. Ignore errors when nothing
|
|
850
|
+
# exists.
|
|
851
|
+
"$CLAUDE_CLI_COMMAND" mcp remove weppy-roblox-mcp >/dev/null 2>&1 || true
|
|
852
|
+
# Capture the CLI exit code immediately so it isn't overwritten by the
|
|
853
|
+
# subsequent grep check (which would otherwise leak its own exit code).
|
|
854
|
+
claude_exit_code=0
|
|
855
|
+
"$CLAUDE_CLI_COMMAND" mcp add weppy-roblox-mcp -- npx -y "@weppy/roblox-mcp@latest" 2>"$claude_stderr_file" || claude_exit_code=$?
|
|
856
|
+
if [ "$claude_exit_code" -eq 0 ]; then
|
|
857
|
+
success "Registered: $app_name"
|
|
858
|
+
elif grep -qi "already exists" "$claude_stderr_file"; then
|
|
859
|
+
# Already registered in another scope — not a failure
|
|
860
|
+
success "Already configured: $app_name"
|
|
861
|
+
else
|
|
862
|
+
fail "Failed: $app_name (exit=$claude_exit_code)"
|
|
863
|
+
printf " CLI: %s\n" "$CLAUDE_CLI_COMMAND"
|
|
864
|
+
printf " stderr:\n"
|
|
865
|
+
sed 's/^/ /' "$claude_stderr_file" || true
|
|
866
|
+
fi
|
|
867
|
+
rm -f "$claude_stderr_file"
|
|
823
868
|
else
|
|
824
|
-
fail "Failed: $app_name"
|
|
869
|
+
fail "Failed: $app_name (claude CLI not found)"
|
|
825
870
|
fi
|
|
826
871
|
;;
|
|
827
872
|
claude-desktop)
|
|
@@ -846,7 +891,7 @@ else
|
|
|
846
891
|
fi
|
|
847
892
|
if is_codex_config_configured "$CODEX_CONFIG"; then
|
|
848
893
|
:
|
|
849
|
-
elif [ -n "$CODEX_CLI_COMMAND" ] && "$CODEX_CLI_COMMAND" mcp add weppy-roblox-mcp -- npx -y @weppy/roblox-mcp 2>/dev/null; then
|
|
894
|
+
elif [ -n "$CODEX_CLI_COMMAND" ] && "$CODEX_CLI_COMMAND" mcp add weppy-roblox-mcp -- npx -y "@weppy/roblox-mcp@latest" 2>/dev/null; then
|
|
850
895
|
success "Registered: $app_name"
|
|
851
896
|
elif is_codex_config_configured "$CODEX_CONFIG"; then
|
|
852
897
|
success "Already configured: $app_name"
|
package/llms-full.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# WEPPY — Canonical Web Docs
|
|
2
2
|
|
|
3
|
-
> WEPPY is an MCP server that lets AI coding agents control a live Roblox Studio session and interact with scripts, instances, terrain, lighting, assets, audio, and
|
|
3
|
+
> WEPPY is an MCP server that lets AI coding agents control a live Roblox Studio session and interact with scripts, instances, terrain, lighting, assets, audio, animations, and UI Studio workflows.
|
|
4
4
|
|
|
5
5
|
This package intentionally does not ship a standalone full documentation surface.
|
|
6
6
|
|
|
@@ -11,4 +11,3 @@ Canonical documentation lives on the web:
|
|
|
11
11
|
- Pro plans: https://weppyai.com/en/plans
|
|
12
12
|
|
|
13
13
|
For app-specific setup, open the web install page and choose the relevant AI client guide.
|
|
14
|
-
|
package/llms.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> WEPPY is an MCP server that lets AI coding agents (Claude, Codex,
|
|
4
4
|
> Cursor, Gemini) control a live Roblox Studio session — create and edit scripts,
|
|
5
|
-
> instances, terrain, lighting, assets, audio, and
|
|
5
|
+
> instances, terrain, lighting, assets, audio, animations, and UI Studio workflows via natural language.
|
|
6
6
|
|
|
7
7
|
## What it does
|
|
8
8
|
|
|
@@ -14,6 +14,7 @@ terrain, and more — inside a live Studio session.
|
|
|
14
14
|
- Action-based MCP tools covering the full Roblox Studio API surface
|
|
15
15
|
- Bidirectional project sync between Roblox Studio and local files (Pro)
|
|
16
16
|
- Automated playtest: AI starts/stops Play/Run, injects test scripts, collects logs
|
|
17
|
+
- UI Studio: AI creates in-game UI that matches your game's style or analyzes existing UI and suggests improvements
|
|
17
18
|
- Web dashboard: monitor AI actions, tool history, sync state, and changelog in real time at `http://localhost:3002`
|
|
18
19
|
- Multi-place support (up to 3 places simultaneously)
|
|
19
20
|
- Roblox Explorer VSCode extension for browsing Studio hierarchy
|
|
@@ -40,7 +41,7 @@ Safari / Firefox users should open the web install page and follow the browser-s
|
|
|
40
41
|
## Tiers
|
|
41
42
|
|
|
42
43
|
- Basic (Free): MCP tool execution + one-way Studio -> Local sync
|
|
43
|
-
- Pro: Bidirectional sync, bulk actions, terrain generation, spatial analysis, multi-place support, advanced tools
|
|
44
|
+
- Pro: Bidirectional sync, UI Studio, bulk actions, terrain generation, spatial analysis, multi-place support, advanced tools
|
|
44
45
|
|
|
45
46
|
## Detailed docs
|
|
46
47
|
|
|
@@ -56,7 +57,7 @@ Safari / Firefox users should open the web install page and follow the browser-s
|
|
|
56
57
|
## Optional
|
|
57
58
|
|
|
58
59
|
- Roblox Explorer VSCode extension (browse synced instance tree)
|
|
59
|
-
- Pro subscription for bidirectional sync and advanced tools
|
|
60
|
+
- Pro subscription for bidirectional sync, UI Studio, and advanced tools
|
|
60
61
|
|
|
61
62
|
## Links
|
|
62
63
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weppy/roblox-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "MCP (Model Context Protocol) server for Roblox Studio integration - enables AI coding agents to interact with Roblox Studio in real-time",
|
|
5
5
|
"main": "plugins/weppy-roblox-mcp/dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r,a as R,u as M,b as B,c as V,j as e}from"./index-B-nqZCE3.js";import{I as G}from"./InfoLabel-CMSu30c4.js";import{G as D}from"./GameChangeDetail-DGphCFbI.js";import{T as m}from"./TooltipText-Bnvm1FcC.js";function F(n){const s={scriptsModified:0,scriptsCreated:0,instancesCreated:0,instancesDeleted:0,instancesMoved:0,propertiesChanged:0,lightingChanged:!1,terrainChanged:!1,assetsInserted:0};for(const l of n)switch(l.category){case"script":l.changeType==="create"?s.scriptsCreated++:s.scriptsModified++;break;case"instance":l.changeType==="create"?s.instancesCreated++:l.changeType==="delete"?s.instancesDeleted++:l.changeType==="move"&&s.instancesMoved++;break;case"property":s.propertiesChanged++;break;case"lighting":s.lightingChanged=!0;break;case"terrain":s.terrainChanged=!0;break;case"asset":s.assetsInserted++;break}return s}function P(n){const[s,l]=r.useState(""),[a,d]=r.useState(""),[u,q]=r.useState(),[w,y]=r.useState("completed"),[I,E]=r.useState([]),[L,O]=r.useState([]),[x,f]=r.useState([]),[p,_]=r.useState(),[j,b]=r.useState(),[v,S]=r.useState(),[C,N]=r.useState({scriptsModified:0,scriptsCreated:0,instancesCreated:0,instancesDeleted:0,instancesMoved:0,propertiesChanged:0,lightingChanged:!1,terrainChanged:!1,assetsInserted:0}),[T,g]=r.useState(!0),[k,h]=r.useState(null),i=r.useCallback(async()=>{if(n){g(!0),h(null);try{const[c,o]=await Promise.all([R.get(`/api/dashboard/changelog/${n}`),R.get(`/api/dashboard/changelog/${n}/changes`)]);l(c.entryId),d(c.startTime),q(c.endTime),y(c.status),E(c.entries),O(c.failures),_(c.contextSummary),b(c.replayMetadata),S(c.verificationSummary),f(o.changes),N(F(o.changes))}catch(c){h(c instanceof Error?c.message:"Failed to load changelog detail")}finally{g(!1)}}},[n]);return r.useEffect(()=>{i()},[i]),{entryId:s,startTime:a,endTime:u,status:w,entries:I,failures:L,changes:x,changeSummary:C,contextSummary:p,replayMetadata:j,verificationSummary:v,loading:T,error:k,refresh:i}}const U={script:"📝",instance:"🧱",property:"🎨",lighting:"🌅",terrain:"⛰️",asset:"📦"};function H(n){return U[n]??"❓"}const W="_page_q2jbi_2",Y="_header_q2jbi_10",z="_backLink_q2jbi_16",J="_headerTitle_q2jbi_29",Q="_headerTime_q2jbi_37",X="_statusActive_q2jbi_44",Z="_statusCompleted_q2jbi_49",ee="_section_q2jbi_54",te="_sectionTitle_q2jbi_61",ne="_summaryGrid_q2jbi_74",ae="_summaryCard_q2jbi_80",ie="_summaryCardActive_q2jbi_94",se="_summaryIcon_q2jbi_99",ce="_summaryCount_q2jbi_105",re="_summaryLabel_q2jbi_112",oe="_contextGrid_q2jbi_121",le="_contextRow_q2jbi_127",de="_contextKey_q2jbi_133",me="_contextValue_q2jbi_141",ge="_timelineFilter_q2jbi_149",he="_filterLabel_q2jbi_156",ue="_filterSelect_q2jbi_162",ye="_timeline_q2jbi_149",xe="_timelineEntry_q2jbi_182",fe="_timelineTime_q2jbi_199",pe="_timelineIcon_q2jbi_207",_e="_timelineBody_q2jbi_212",je="_timelineSummary_q2jbi_217",be="_timelineTarget_q2jbi_224",ve="_timelineConfidence_q2jbi_231",Se="_confidenceExact_q2jbi_240",Ce="_confidencePartial_q2jbi_245",Ne="_confidenceAfterOnly_q2jbi_250",Te="_confidenceIntentOnly_q2jbi_255",ke="_confidenceUnknown_q2jbi_260",qe="_timelineExpanded_q2jbi_266",we="_empty_q2jbi_338",Ie="_loading_q2jbi_347",Ee="_error_q2jbi_356",t={page:W,header:Y,backLink:z,headerTitle:J,headerTime:Q,statusActive:X,statusCompleted:Z,section:ee,sectionTitle:te,summaryGrid:ne,summaryCard:ae,summaryCardActive:ie,summaryIcon:se,summaryCount:ce,summaryLabel:re,contextGrid:oe,contextRow:le,contextKey:de,contextValue:me,timelineFilter:ge,filterLabel:he,filterSelect:ue,timeline:ye,timelineEntry:xe,timelineTime:fe,timelineIcon:pe,timelineBody:_e,timelineSummary:je,timelineTarget:be,timelineConfidence:ve,confidenceExact:Se,confidencePartial:Ce,confidenceAfterOnly:Ne,confidenceIntentOnly:Te,confidenceUnknown:ke,timelineExpanded:qe,empty:we,loading:Ie,error:Ee},$=[{key:"script",icon:"📝",labelKey:"changelog.category.script"},{key:"instance",icon:"🧱",labelKey:"changelog.category.instance"},{key:"property",icon:"🎨",labelKey:"changelog.category.property"},{key:"lighting",icon:"🌅",labelKey:"changelog.category.lighting"},{key:"terrain",icon:"⛰️",labelKey:"changelog.category.terrain"},{key:"asset",icon:"📦",labelKey:"changelog.category.asset"}];function A(n){if(!n)return"--:--";const s=new Date(n);return`${String(s.getHours()).padStart(2,"0")}:${String(s.getMinutes()).padStart(2,"0")}`}function Le(n){if(!n)return"--:--:--";const s=new Date(n);return`${String(s.getHours()).padStart(2,"0")}:${String(s.getMinutes()).padStart(2,"0")}:${String(s.getSeconds()).padStart(2,"0")}`}function Oe(n,s){if(!n||!s)return"";const l=new Date(s).getTime()-new Date(n).getTime();return l<0?"":`${Math.round(l/6e4)}min`}function Ae(n){switch(n){case"exact":return t.confidenceExact;case"partial":return t.confidencePartial;case"after-only":return t.confidenceAfterOnly;case"intent-only":return t.confidenceIntentOnly;default:return t.confidenceUnknown}}function Ke(n,s){switch(s){case"exact":return n("changelog.detail.confidence.exact","Exact");case"partial":return n("changelog.detail.confidence.partial","Partial");case"after-only":return n("changelog.detail.confidence.afterOnly","After only");case"intent-only":return n("changelog.detail.confidence.intentOnly","Intent only");default:return n("changelog.detail.confidence.unknown","Unknown")}}function Re(n,s){switch(s){case"exact":return n("changelog.detail.confidence.exact.tooltip","Both the before and after state were confirmed for this change.");case"partial":return n("changelog.detail.confidence.partial.tooltip","Only part of the before and after state could be confirmed for this change.");case"after-only":return n("changelog.detail.confidence.afterOnly.tooltip","Only the resulting state after the change could be confirmed.");case"intent-only":return n("changelog.detail.confidence.intentOnly.tooltip","Only the requested action was recorded, not the resulting state.");default:return n("changelog.detail.confidence.unknown.tooltip","This change could not be confidently classified from the available data.")}}function Ge(){var x,f,p,_,j,b,v,S,C,N,T,g,k,h;const{t:n}=M(),{id:s}=B(),l=V(),a=P(s),[d,u]=r.useState("all"),[q,w]=r.useState(null),y=r.useMemo(()=>[...d==="all"?a.changes:a.changes.filter(c=>c.category===d)].reverse(),[a.changes,d]);if(a.loading)return e.jsx("div",{className:t.loading,children:n("common.loading","Loading...")});if(a.error)return e.jsxs("div",{className:t.error,children:[a.error,e.jsx("br",{}),e.jsxs("span",{className:t.backLink,onClick:()=>l("/changelog"),children:["←"," ",n("changelog.detail.backToList","Back to list")]})]});const I=Oe(a.startTime,a.endTime),E=a.endTime?`${A(a.startTime)} → ${A(a.endTime)} (${I})`:`${A(a.startTime)} → ${n("changelog.card.inProgress","in progress")}`,L=!!((x=a.contextSummary)!=null&&x.intent||(f=a.contextSummary)!=null&&f.testScenario||(p=a.contextSummary)!=null&&p.expectedBehavior||(_=a.contextSummary)!=null&&_.observedBehavior),O=!!((j=a.verificationSummary)!=null&&j.label||(b=a.verificationSummary)!=null&&b.status||(v=a.verificationSummary)!=null&&v.testTimestamp);return e.jsxs("div",{className:t.page,children:[e.jsxs("div",{className:t.header,children:[e.jsxs("span",{className:t.backLink,onClick:()=>l("/changelog"),children:["←"," ",n("sidebar.changelog","Changelog")]}),e.jsx("span",{className:t.headerTitle,children:"|"}),e.jsx("span",{className:t.headerTime,children:E}),e.jsx(m,{text:a.status==="active"?n("changelog.card.active.tooltip","This session is still receiving new game changes."):n("changelog.card.completed.tooltip","This session has ended and no more changes are expected."),children:e.jsx("span",{className:a.status==="active"?t.statusActive:t.statusCompleted,children:a.status==="active"?n("changelog.card.active","Active"):n("changelog.card.completed","Completed")})})]}),e.jsxs("div",{className:t.section,children:[e.jsx("div",{className:t.sectionTitle,children:e.jsx(m,{text:n("changelog.detail.changeSummary.tooltip","Counts of extracted game changes grouped by category for this session."),children:e.jsx("span",{children:n("changelog.detail.changeSummary","Change Summary")})})}),e.jsx("div",{className:t.summaryGrid,children:$.map(i=>{const c=a.changeSummary;let o;switch(i.key){case"script":o=c.scriptsModified+c.scriptsCreated;break;case"instance":o=c.instancesCreated+c.instancesDeleted+c.instancesMoved;break;case"property":o=c.propertiesChanged;break;case"lighting":o=c.lightingChanged?1:0;break;case"terrain":o=c.terrainChanged?1:0;break;case"asset":o=c.assetsInserted;break;default:o=0}const K=d===i.key;return e.jsxs("div",{className:`${t.summaryCard} ${K?t.summaryCardActive:""}`,onClick:()=>u(K?"all":i.key),children:[e.jsx("span",{className:t.summaryIcon,children:i.icon}),e.jsx("div",{className:t.summaryCount,children:o}),e.jsx("div",{className:t.summaryLabel,children:n(i.labelKey,i.key)})]},i.key)})})]}),L&&e.jsxs("div",{className:t.section,children:[e.jsx("div",{className:t.sectionTitle,children:e.jsx(m,{text:n("changelog.detail.context.tooltip","Structured execution context captured for this changelog session."),children:e.jsx("span",{children:n("changelog.detail.context.title","Context Summary")})})}),e.jsxs("div",{className:t.contextGrid,children:[((S=a.contextSummary)==null?void 0:S.intent)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("changelog.card.sessionIntent","Session intent")}),e.jsx("span",{className:t.contextValue,children:a.contextSummary.intent})]}),((C=a.contextSummary)==null?void 0:C.testScenario)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("playtest.context.why","Why this test ran")}),e.jsx("span",{className:t.contextValue,children:a.contextSummary.testScenario})]}),((N=a.contextSummary)==null?void 0:N.expectedBehavior)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("playtest.context.expected","Expected")}),e.jsx("span",{className:t.contextValue,children:a.contextSummary.expectedBehavior})]}),((T=a.contextSummary)==null?void 0:T.observedBehavior)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("playtest.context.observed","Observed")}),e.jsx("span",{className:t.contextValue,children:a.contextSummary.observedBehavior})]})]})]}),O&&e.jsxs("div",{className:t.section,children:[e.jsx("div",{className:t.sectionTitle,children:e.jsx(m,{text:n("changelog.detail.verification.tooltip","Verification signals linked to this changelog session."),children:e.jsx("span",{children:n("changelog.detail.verification.title","Verification")})})}),e.jsxs("div",{className:t.contextGrid,children:[((g=a.verificationSummary)==null?void 0:g.label)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("changelog.detail.verification.label","Result")}),e.jsx("span",{className:t.contextValue,children:a.verificationSummary.label})]}),((k=a.verificationSummary)==null?void 0:k.status)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("changelog.detail.verification.status","Status")}),e.jsx("span",{className:t.contextValue,children:a.verificationSummary.status})]}),((h=a.verificationSummary)==null?void 0:h.testTimestamp)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("changelog.detail.verification.timestamp","Recorded at")}),e.jsx("span",{className:t.contextValue,children:a.verificationSummary.testTimestamp})]})]})]}),e.jsxs("div",{className:t.section,children:[e.jsx("div",{className:t.sectionTitle,children:e.jsx(m,{text:n("changelog.detail.changeTimeline.tooltip","Chronological list of extracted game changes for this session."),children:e.jsx("span",{children:n("changelog.detail.changeTimeline","Change Timeline")})})}),e.jsxs("div",{className:t.timelineFilter,children:[e.jsx("span",{className:t.filterLabel,children:e.jsx(G,{label:`${n("changelog.detail.filterCategory","Category")}:`,tooltip:n("changelog.detail.filterCategory.tooltip","Filter the timeline to a single change category.")})}),e.jsxs("select",{className:t.filterSelect,value:d,onChange:i=>u(i.target.value),children:[e.jsx("option",{value:"all",children:n("tools.filter.all","All")}),$.map(i=>e.jsxs("option",{value:i.key,children:[i.icon," ",n(i.labelKey,i.key)]},i.key))]})]}),y.length===0?e.jsx("div",{className:t.empty,children:n("changelog.detail.noChanges","No changes in this category")}):e.jsx("div",{className:t.timeline,children:y.map((i,c)=>{const o=q===c;return e.jsxs("div",{children:[e.jsxs("div",{className:t.timelineEntry,onClick:()=>w(o?null:c),children:[e.jsx("span",{className:t.timelineTime,children:Le(i.timestamp)}),e.jsx("span",{className:t.timelineIcon,children:H(i.category)}),e.jsxs("div",{className:t.timelineBody,children:[e.jsxs("div",{className:t.timelineSummary,children:[i.summary,e.jsx(m,{text:Re(n,i.confidence),children:e.jsx("span",{className:`${t.timelineConfidence} ${Ae(i.confidence)}`,children:Ke(n,i.confidence)})})]}),e.jsx("div",{className:t.timelineTarget,children:i.target})]})]}),o&&e.jsx("div",{className:t.timelineExpanded,children:e.jsx(D,{change:i})})]},c)})})]})]})}export{Ge as Component};
|
|
1
|
+
import{r,a as R,u as M,b as B,c as V,j as e}from"./index-FfT2oWAB.js";import{I as G}from"./InfoLabel-B9KT3kfU.js";import{G as D}from"./GameChangeDetail-D1kE2L_v.js";import{T as m}from"./TooltipText-Baw38jOU.js";function F(n){const s={scriptsModified:0,scriptsCreated:0,instancesCreated:0,instancesDeleted:0,instancesMoved:0,propertiesChanged:0,lightingChanged:!1,terrainChanged:!1,assetsInserted:0};for(const l of n)switch(l.category){case"script":l.changeType==="create"?s.scriptsCreated++:s.scriptsModified++;break;case"instance":l.changeType==="create"?s.instancesCreated++:l.changeType==="delete"?s.instancesDeleted++:l.changeType==="move"&&s.instancesMoved++;break;case"property":s.propertiesChanged++;break;case"lighting":s.lightingChanged=!0;break;case"terrain":s.terrainChanged=!0;break;case"asset":s.assetsInserted++;break}return s}function P(n){const[s,l]=r.useState(""),[a,d]=r.useState(""),[u,q]=r.useState(),[w,y]=r.useState("completed"),[I,E]=r.useState([]),[L,O]=r.useState([]),[x,f]=r.useState([]),[p,_]=r.useState(),[j,b]=r.useState(),[v,S]=r.useState(),[C,N]=r.useState({scriptsModified:0,scriptsCreated:0,instancesCreated:0,instancesDeleted:0,instancesMoved:0,propertiesChanged:0,lightingChanged:!1,terrainChanged:!1,assetsInserted:0}),[T,g]=r.useState(!0),[k,h]=r.useState(null),i=r.useCallback(async()=>{if(n){g(!0),h(null);try{const[c,o]=await Promise.all([R.get(`/api/dashboard/changelog/${n}`),R.get(`/api/dashboard/changelog/${n}/changes`)]);l(c.entryId),d(c.startTime),q(c.endTime),y(c.status),E(c.entries),O(c.failures),_(c.contextSummary),b(c.replayMetadata),S(c.verificationSummary),f(o.changes),N(F(o.changes))}catch(c){h(c instanceof Error?c.message:"Failed to load changelog detail")}finally{g(!1)}}},[n]);return r.useEffect(()=>{i()},[i]),{entryId:s,startTime:a,endTime:u,status:w,entries:I,failures:L,changes:x,changeSummary:C,contextSummary:p,replayMetadata:j,verificationSummary:v,loading:T,error:k,refresh:i}}const U={script:"📝",instance:"🧱",property:"🎨",lighting:"🌅",terrain:"⛰️",asset:"📦"};function H(n){return U[n]??"❓"}const W="_page_q2jbi_2",Y="_header_q2jbi_10",z="_backLink_q2jbi_16",J="_headerTitle_q2jbi_29",Q="_headerTime_q2jbi_37",X="_statusActive_q2jbi_44",Z="_statusCompleted_q2jbi_49",ee="_section_q2jbi_54",te="_sectionTitle_q2jbi_61",ne="_summaryGrid_q2jbi_74",ae="_summaryCard_q2jbi_80",ie="_summaryCardActive_q2jbi_94",se="_summaryIcon_q2jbi_99",ce="_summaryCount_q2jbi_105",re="_summaryLabel_q2jbi_112",oe="_contextGrid_q2jbi_121",le="_contextRow_q2jbi_127",de="_contextKey_q2jbi_133",me="_contextValue_q2jbi_141",ge="_timelineFilter_q2jbi_149",he="_filterLabel_q2jbi_156",ue="_filterSelect_q2jbi_162",ye="_timeline_q2jbi_149",xe="_timelineEntry_q2jbi_182",fe="_timelineTime_q2jbi_199",pe="_timelineIcon_q2jbi_207",_e="_timelineBody_q2jbi_212",je="_timelineSummary_q2jbi_217",be="_timelineTarget_q2jbi_224",ve="_timelineConfidence_q2jbi_231",Se="_confidenceExact_q2jbi_240",Ce="_confidencePartial_q2jbi_245",Ne="_confidenceAfterOnly_q2jbi_250",Te="_confidenceIntentOnly_q2jbi_255",ke="_confidenceUnknown_q2jbi_260",qe="_timelineExpanded_q2jbi_266",we="_empty_q2jbi_338",Ie="_loading_q2jbi_347",Ee="_error_q2jbi_356",t={page:W,header:Y,backLink:z,headerTitle:J,headerTime:Q,statusActive:X,statusCompleted:Z,section:ee,sectionTitle:te,summaryGrid:ne,summaryCard:ae,summaryCardActive:ie,summaryIcon:se,summaryCount:ce,summaryLabel:re,contextGrid:oe,contextRow:le,contextKey:de,contextValue:me,timelineFilter:ge,filterLabel:he,filterSelect:ue,timeline:ye,timelineEntry:xe,timelineTime:fe,timelineIcon:pe,timelineBody:_e,timelineSummary:je,timelineTarget:be,timelineConfidence:ve,confidenceExact:Se,confidencePartial:Ce,confidenceAfterOnly:Ne,confidenceIntentOnly:Te,confidenceUnknown:ke,timelineExpanded:qe,empty:we,loading:Ie,error:Ee},$=[{key:"script",icon:"📝",labelKey:"changelog.category.script"},{key:"instance",icon:"🧱",labelKey:"changelog.category.instance"},{key:"property",icon:"🎨",labelKey:"changelog.category.property"},{key:"lighting",icon:"🌅",labelKey:"changelog.category.lighting"},{key:"terrain",icon:"⛰️",labelKey:"changelog.category.terrain"},{key:"asset",icon:"📦",labelKey:"changelog.category.asset"}];function A(n){if(!n)return"--:--";const s=new Date(n);return`${String(s.getHours()).padStart(2,"0")}:${String(s.getMinutes()).padStart(2,"0")}`}function Le(n){if(!n)return"--:--:--";const s=new Date(n);return`${String(s.getHours()).padStart(2,"0")}:${String(s.getMinutes()).padStart(2,"0")}:${String(s.getSeconds()).padStart(2,"0")}`}function Oe(n,s){if(!n||!s)return"";const l=new Date(s).getTime()-new Date(n).getTime();return l<0?"":`${Math.round(l/6e4)}min`}function Ae(n){switch(n){case"exact":return t.confidenceExact;case"partial":return t.confidencePartial;case"after-only":return t.confidenceAfterOnly;case"intent-only":return t.confidenceIntentOnly;default:return t.confidenceUnknown}}function Ke(n,s){switch(s){case"exact":return n("changelog.detail.confidence.exact","Exact");case"partial":return n("changelog.detail.confidence.partial","Partial");case"after-only":return n("changelog.detail.confidence.afterOnly","After only");case"intent-only":return n("changelog.detail.confidence.intentOnly","Intent only");default:return n("changelog.detail.confidence.unknown","Unknown")}}function Re(n,s){switch(s){case"exact":return n("changelog.detail.confidence.exact.tooltip","Both the before and after state were confirmed for this change.");case"partial":return n("changelog.detail.confidence.partial.tooltip","Only part of the before and after state could be confirmed for this change.");case"after-only":return n("changelog.detail.confidence.afterOnly.tooltip","Only the resulting state after the change could be confirmed.");case"intent-only":return n("changelog.detail.confidence.intentOnly.tooltip","Only the requested action was recorded, not the resulting state.");default:return n("changelog.detail.confidence.unknown.tooltip","This change could not be confidently classified from the available data.")}}function Ge(){var x,f,p,_,j,b,v,S,C,N,T,g,k,h;const{t:n}=M(),{id:s}=B(),l=V(),a=P(s),[d,u]=r.useState("all"),[q,w]=r.useState(null),y=r.useMemo(()=>[...d==="all"?a.changes:a.changes.filter(c=>c.category===d)].reverse(),[a.changes,d]);if(a.loading)return e.jsx("div",{className:t.loading,children:n("common.loading","Loading...")});if(a.error)return e.jsxs("div",{className:t.error,children:[a.error,e.jsx("br",{}),e.jsxs("span",{className:t.backLink,onClick:()=>l("/changelog"),children:["←"," ",n("changelog.detail.backToList","Back to list")]})]});const I=Oe(a.startTime,a.endTime),E=a.endTime?`${A(a.startTime)} → ${A(a.endTime)} (${I})`:`${A(a.startTime)} → ${n("changelog.card.inProgress","in progress")}`,L=!!((x=a.contextSummary)!=null&&x.intent||(f=a.contextSummary)!=null&&f.testScenario||(p=a.contextSummary)!=null&&p.expectedBehavior||(_=a.contextSummary)!=null&&_.observedBehavior),O=!!((j=a.verificationSummary)!=null&&j.label||(b=a.verificationSummary)!=null&&b.status||(v=a.verificationSummary)!=null&&v.testTimestamp);return e.jsxs("div",{className:t.page,children:[e.jsxs("div",{className:t.header,children:[e.jsxs("span",{className:t.backLink,onClick:()=>l("/changelog"),children:["←"," ",n("sidebar.changelog","Changelog")]}),e.jsx("span",{className:t.headerTitle,children:"|"}),e.jsx("span",{className:t.headerTime,children:E}),e.jsx(m,{text:a.status==="active"?n("changelog.card.active.tooltip","This session is still receiving new game changes."):n("changelog.card.completed.tooltip","This session has ended and no more changes are expected."),children:e.jsx("span",{className:a.status==="active"?t.statusActive:t.statusCompleted,children:a.status==="active"?n("changelog.card.active","Active"):n("changelog.card.completed","Completed")})})]}),e.jsxs("div",{className:t.section,children:[e.jsx("div",{className:t.sectionTitle,children:e.jsx(m,{text:n("changelog.detail.changeSummary.tooltip","Counts of extracted game changes grouped by category for this session."),children:e.jsx("span",{children:n("changelog.detail.changeSummary","Change Summary")})})}),e.jsx("div",{className:t.summaryGrid,children:$.map(i=>{const c=a.changeSummary;let o;switch(i.key){case"script":o=c.scriptsModified+c.scriptsCreated;break;case"instance":o=c.instancesCreated+c.instancesDeleted+c.instancesMoved;break;case"property":o=c.propertiesChanged;break;case"lighting":o=c.lightingChanged?1:0;break;case"terrain":o=c.terrainChanged?1:0;break;case"asset":o=c.assetsInserted;break;default:o=0}const K=d===i.key;return e.jsxs("div",{className:`${t.summaryCard} ${K?t.summaryCardActive:""}`,onClick:()=>u(K?"all":i.key),children:[e.jsx("span",{className:t.summaryIcon,children:i.icon}),e.jsx("div",{className:t.summaryCount,children:o}),e.jsx("div",{className:t.summaryLabel,children:n(i.labelKey,i.key)})]},i.key)})})]}),L&&e.jsxs("div",{className:t.section,children:[e.jsx("div",{className:t.sectionTitle,children:e.jsx(m,{text:n("changelog.detail.context.tooltip","Structured execution context captured for this changelog session."),children:e.jsx("span",{children:n("changelog.detail.context.title","Context Summary")})})}),e.jsxs("div",{className:t.contextGrid,children:[((S=a.contextSummary)==null?void 0:S.intent)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("changelog.card.sessionIntent","Session intent")}),e.jsx("span",{className:t.contextValue,children:a.contextSummary.intent})]}),((C=a.contextSummary)==null?void 0:C.testScenario)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("playtest.context.why","Why this test ran")}),e.jsx("span",{className:t.contextValue,children:a.contextSummary.testScenario})]}),((N=a.contextSummary)==null?void 0:N.expectedBehavior)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("playtest.context.expected","Expected")}),e.jsx("span",{className:t.contextValue,children:a.contextSummary.expectedBehavior})]}),((T=a.contextSummary)==null?void 0:T.observedBehavior)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("playtest.context.observed","Observed")}),e.jsx("span",{className:t.contextValue,children:a.contextSummary.observedBehavior})]})]})]}),O&&e.jsxs("div",{className:t.section,children:[e.jsx("div",{className:t.sectionTitle,children:e.jsx(m,{text:n("changelog.detail.verification.tooltip","Verification signals linked to this changelog session."),children:e.jsx("span",{children:n("changelog.detail.verification.title","Verification")})})}),e.jsxs("div",{className:t.contextGrid,children:[((g=a.verificationSummary)==null?void 0:g.label)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("changelog.detail.verification.label","Result")}),e.jsx("span",{className:t.contextValue,children:a.verificationSummary.label})]}),((k=a.verificationSummary)==null?void 0:k.status)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("changelog.detail.verification.status","Status")}),e.jsx("span",{className:t.contextValue,children:a.verificationSummary.status})]}),((h=a.verificationSummary)==null?void 0:h.testTimestamp)&&e.jsxs("div",{className:t.contextRow,children:[e.jsx("span",{className:t.contextKey,children:n("changelog.detail.verification.timestamp","Recorded at")}),e.jsx("span",{className:t.contextValue,children:a.verificationSummary.testTimestamp})]})]})]}),e.jsxs("div",{className:t.section,children:[e.jsx("div",{className:t.sectionTitle,children:e.jsx(m,{text:n("changelog.detail.changeTimeline.tooltip","Chronological list of extracted game changes for this session."),children:e.jsx("span",{children:n("changelog.detail.changeTimeline","Change Timeline")})})}),e.jsxs("div",{className:t.timelineFilter,children:[e.jsx("span",{className:t.filterLabel,children:e.jsx(G,{label:`${n("changelog.detail.filterCategory","Category")}:`,tooltip:n("changelog.detail.filterCategory.tooltip","Filter the timeline to a single change category.")})}),e.jsxs("select",{className:t.filterSelect,value:d,onChange:i=>u(i.target.value),children:[e.jsx("option",{value:"all",children:n("tools.filter.all","All")}),$.map(i=>e.jsxs("option",{value:i.key,children:[i.icon," ",n(i.labelKey,i.key)]},i.key))]})]}),y.length===0?e.jsx("div",{className:t.empty,children:n("changelog.detail.noChanges","No changes in this category")}):e.jsx("div",{className:t.timeline,children:y.map((i,c)=>{const o=q===c;return e.jsxs("div",{children:[e.jsxs("div",{className:t.timelineEntry,onClick:()=>w(o?null:c),children:[e.jsx("span",{className:t.timelineTime,children:Le(i.timestamp)}),e.jsx("span",{className:t.timelineIcon,children:H(i.category)}),e.jsxs("div",{className:t.timelineBody,children:[e.jsxs("div",{className:t.timelineSummary,children:[i.summary,e.jsx(m,{text:Re(n,i.confidence),children:e.jsx("span",{className:`${t.timelineConfidence} ${Ae(i.confidence)}`,children:Ke(n,i.confidence)})})]}),e.jsx("div",{className:t.timelineTarget,children:i.target})]})]}),o&&e.jsx("div",{className:t.timelineExpanded,children:e.jsx(D,{change:i})})]},c)})})]})]})}export{Ge as Component};
|