yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
panel_admission.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <map>
5#include <set>
6
7#include "app/editor/editor.h"
11
12namespace yaze {
13namespace editor {
14
15namespace {
16
17// Keep entries grouped by editor, in the order of the editor's default preset.
18// A reason names the workflow the window serves and why no existing surface
19// hosts it. "Current default" entries record windows that were already visible
20// on first open when the admission policy landed; the audit document
21// (docs/internal/plans/panel-admission-audit-2026-09-29.md) tracks which of
22// them are candidates for demotion.
23const std::vector<PanelAdmissionEntry> kFirstClassAllowlist = {
24 // Overworld
25 {"overworld.canvas", "Primary map canvas; the editor's work surface."},
26 {"overworld.tile16_selector",
27 "Tile16 palette the canvas paints from; docked beside the canvas in the "
28 "Overworld Default preset."},
29 {"overworld.scratch",
30 "Scratch workspace for staging, assembling, and selecting tile "
31 "compositions beside the canvas."},
32 {"overworld.properties",
33 "Current default: selected-map properties in the Overworld Default "
34 "preset."},
35 {"overworld.area_graphics",
36 "Reviewed Overworld default: selected-area graphics beside the map; "
37 "shares the right-bottom dock with map properties instead of covering "
38 "the canvas with a bottom strip."},
39
40 // Dungeon
41 {"dungeon.workbench",
42 "Single default dungeon surface; hosts the room browser, canvas, and "
43 "inspector tools."},
44
45 // Graphics
46 {"graphics.sheet_browser_v2",
47 "Sheet list that selects what the pixel editor edits."},
48 {"graphics.pixel_editor", "Primary graphics sheet editing surface."},
49
50 // Palette
51 {"palette.control_panel",
52 "Single Palette workspace; hosts group navigation and color editing."},
53
54 // Sprite
55 {"sprite.vanilla_editor", "Primary sprite editing surface."},
56
57 // Screen
58 {"screen.dungeon_maps",
59 "Primary surface of the Screen editor's default preset."},
60 {"screen.title_screen",
61 "Opened by ScreenEditor::Initialize, so it is what actually shows on "
62 "first open (the preset lists dungeon_maps); resolve in the audit doc."},
63
64 // Music
65 {"music.song_browser", "Song list that selects what the tracker edits."},
66 {"music.tracker", "Playback transport for the Music Default preset."},
67 {"music.piano_roll", "Primary music editing surface."},
68
69 // Message
70 {"message.message_list",
71 "Message list that selects what the editor edits."},
72 {"message.message_editor", "Primary message text editing surface."},
73
74 // Assembly
75 {"assembly.code_editor", "Primary assembly source editing surface."},
76 {"assembly.file_browser", "Project file tree that opens sources."},
77 {"assembly.disassembly",
78 "Current default: disassembly view in the Assembly Default preset."},
79 {"assembly.toolbar",
80 "Current default: registered visible. Its Open Folder and Save buttons "
81 "are the only quick actions when no project code folder is set."},
82
83 // Planning
84 {"planning.overview",
85 "Primary planning surface: goal, milestones, route strip and decisions."},
86 {"planning.route",
87 "Route step and item editor; the Overview's route strip selects into it."},
88
89 // Shell
90 {"dashboard.main", "Editor picker shown at startup and on Ctrl+E."},
91
92 // Emulator and memory
93 {"emulator.ppu_viewer", "Primary emulator output surface."},
94 {"memory.hex_editor", "Primary surface of the Memory editor."},
95
96 // Agent
97 {"agent.chat", "Primary agent conversation surface."},
98 {"agent.configuration",
99 "Current default: agent provider setup in the Agent preset."},
100 {"agent.status", "Current default: agent status in the Agent preset."},
101};
102
103// panel-admission: deferred-begin
104// The Overworld panels are Codex-coordinated: src/app/editor/overworld/* is
105// frozen for edits until scawful approves, and Codex has in-flight branches
106// there (codex/overworld-large-selection-preview, codex/terrain-catalog).
107// Proposals for each window are in the audit document.
108constexpr char kOverworldFrozen[] =
109 "Overworld panels are Codex-coordinated; src/app/editor/overworld/* needs "
110 "scawful's approval before edits.";
111const std::vector<PanelAdmissionEntry> kDeferredDeclarations = {
112 {"overworld.canvas", kOverworldFrozen},
113 {"overworld.tile16_selector", kOverworldFrozen},
114 {"overworld.properties", kOverworldFrozen},
115 {"overworld.tile16_editor", kOverworldFrozen},
116 {"overworld.tile8_selector", kOverworldFrozen},
117 {"overworld.area_graphics", kOverworldFrozen},
118 {"overworld.scratch", kOverworldFrozen},
119 {"overworld.gfx_groups", kOverworldFrozen},
120 {"overworld.usage_stats", kOverworldFrozen},
121 {"overworld.item_list", kOverworldFrozen},
122 {"overworld.v3_settings", kOverworldFrozen},
123 {"overworld.debug", kOverworldFrozen},
124 {"overworld.entity_workbench", kOverworldFrozen},
125};
126// panel-admission: deferred-end
127
128// Fix texts carry no window id so identical failures group into one block.
129constexpr char kUndeclaredFix[] =
130 "Override GetPresentationPolicy() in the panel class (or pass a policy to "
131 "RegisterWindowContent) and return one of: "
132 "WindowPresentationPolicy::EmbeddedTool().WithHost(\"<surface that embeds "
133 "it>\") for a tool that acts on another surface's selection (inspector "
134 "section, tool tab, canvas context-menu popup); OptionalPopOut() for a "
135 "standalone window users choose to open; "
136 "Diagnostic().WithHost(\"Command palette\") for developer or validation "
137 "tooling. CoreWorkspace() is only for an editor's primary work surface and "
138 "needs a FirstClassPanelAllowlist() entry. See \"Panel admission\" in "
139 "docs/internal/architecture/editor-ui-module-pattern.md.";
140
141constexpr char kCoreFix[] =
142 "Core is reserved for an editor's primary work surface. Either add the "
143 "window id to FirstClassPanelAllowlist() in "
144 "src/app/editor/system/workspace/panel_admission.cc with the workflow it "
145 "serves and why no inspector, tool tab, or context-menu popup can host it, "
146 "or pick EmbeddedTool().WithHost(...) or OptionalPopOut().";
147
148constexpr char kDefaultVisibleFix[] =
149 "Hide it by default: remove IsVisibleByDefault()/visible_by_default and "
150 "its LayoutPresets default_visible_panels entry so it opens from its host "
151 "or the command palette. If it really is a primary surface, add the window "
152 "id to FirstClassPanelAllowlist() in "
153 "src/app/editor/system/workspace/panel_admission.cc with a reason.";
154
155constexpr char kHostFix[] =
156 "Add .WithHost(\"<surface>\") to the policy, for example "
157 "EmbeddedTool().WithHost(\"Dungeon Workbench inspector\") or "
158 "Diagnostic().WithHost(\"Command palette\"). The host is where users find "
159 "the window now that it is not in the window browser.";
160
161} // namespace
162
163const std::vector<PanelAdmissionEntry>& FirstClassPanelAllowlist() {
164 return kFirstClassAllowlist;
165}
166
168 const std::string& window_id) {
169 for (const auto& entry : kFirstClassAllowlist) {
170 if (window_id == entry.window_id) {
171 return &entry;
172 }
173 }
174 return nullptr;
175}
176
177const std::vector<PanelAdmissionEntry>& DeferredPolicyDeclarations() {
178 return kDeferredDeclarations;
179}
180
181bool IsPolicyDeclarationDeferred(const std::string& window_id) {
182 for (const auto& entry : kDeferredDeclarations) {
183 if (window_id == entry.window_id) {
184 return true;
185 }
186 }
187 return false;
188}
189
191 return "[" + category + "] " + window_id + ": " + problem +
192 "\n Fix: " + fix;
193}
194
195std::vector<std::string> CollectDefaultVisibleWindowIds(
196 const WorkspaceWindowManager& window_manager, size_t session_id) {
197 const std::vector<std::string> registered =
198 window_manager.GetWindowsInSession(session_id);
199 std::set<std::string> registered_ids(registered.begin(), registered.end());
200 std::set<std::string> visible;
201
202 for (const auto& window_id : registered) {
203 const WindowDescriptor* descriptor =
204 window_manager.GetWindowDescriptor(session_id, window_id);
205 if (descriptor != nullptr && descriptor->visibility_flag != nullptr &&
206 *descriptor->visibility_flag) {
207 visible.insert(window_id);
208 }
209 }
210
211 constexpr EditorType kPresetEditors[] = {
217 };
218 for (EditorType type : kPresetEditors) {
219 for (const auto& window_id : LayoutPresets::GetDefaultPanels(type)) {
220 if (registered_ids.count(window_id) != 0) {
221 visible.insert(window_id);
222 }
223 }
224 }
225 return std::vector<std::string>(visible.begin(), visible.end());
226}
227
228std::vector<PanelAdmissionFinding> AuditPanelAdmission(
229 const WorkspaceWindowManager& window_manager, size_t session_id) {
230 std::vector<PanelAdmissionFinding> findings;
231
232 const std::vector<std::string> default_visible =
233 CollectDefaultVisibleWindowIds(window_manager, session_id);
234 const std::set<std::string> default_visible_set(default_visible.begin(),
235 default_visible.end());
236
237 std::vector<std::string> window_ids =
238 window_manager.GetWindowsInSession(session_id);
239 std::sort(window_ids.begin(), window_ids.end());
240 window_ids.erase(std::unique(window_ids.begin(), window_ids.end()),
241 window_ids.end());
242
243 for (const auto& window_id : window_ids) {
244 const WindowDescriptor* descriptor =
245 window_manager.GetWindowDescriptor(session_id, window_id);
246 if (descriptor == nullptr) {
247 continue;
248 }
249 const WindowPresentationPolicy& policy = descriptor->presentation;
250 const bool admitted = FindFirstClassPanelEntry(window_id) != nullptr;
251 auto add = [&](std::string problem, std::string fix) {
252 findings.push_back({window_id, descriptor->category, std::move(problem),
253 std::move(fix)});
254 };
255
256 if (!policy.declared && !IsPolicyDeclarationDeferred(window_id)) {
257 add("declares no presentation policy.", kUndeclaredFix);
258 continue;
259 }
260
261 if (policy.role == WindowPresentationRole::kCoreWorkspace && !admitted) {
262 add("declares CoreWorkspace but is not in FirstClassPanelAllowlist().",
263 kCoreFix);
264 }
265
266 if (default_visible_set.count(window_id) != 0 && !admitted) {
267 add(std::string("is visible by default (IsVisibleByDefault(), "
268 "visible_by_default, or a LayoutPresets default) with "
269 "role ") +
271 " but is not in FirstClassPanelAllowlist().",
272 kDefaultVisibleFix);
273 }
274
275 const bool needs_host =
278 if (needs_host && policy.host.empty()) {
279 add(std::string(WindowPresentationRoleName(policy.role)) +
280 " names no host surface.",
281 kHostFix);
282 }
283 }
284 return findings;
285}
286
288 const std::vector<PanelAdmissionFinding>& findings) {
289 // One block per distinct fix so 60 identical failures do not repeat the
290 // instructions 60 times.
291 std::vector<std::string> fix_order;
292 std::map<std::string, std::vector<const PanelAdmissionFinding*>> groups;
293 for (const auto& finding : findings) {
294 auto [it, inserted] = groups.try_emplace(finding.fix);
295 if (inserted) {
296 fix_order.push_back(finding.fix);
297 }
298 it->second.push_back(&finding);
299 }
300
301 std::string out;
302 for (const auto& fix : fix_order) {
303 for (const PanelAdmissionFinding* finding : groups[fix]) {
304 out += " [" + finding->category + "] " + finding->window_id + ": " +
305 finding->problem + "\n";
306 }
307 out += " Fix: " + fix + "\n\n";
308 }
309 return out;
310}
311
312} // namespace editor
313} // namespace yaze
static std::vector< std::string > GetDefaultPanels(EditorType type)
Get default visible panels for an editor.
Central registry for all editor cards with session awareness and dependency injection.
const WindowDescriptor * GetWindowDescriptor(size_t session_id, const std::string &base_window_id) const
std::vector< std::string > GetWindowsInSession(size_t session_id) const
const std::vector< PanelAdmissionEntry > kFirstClassAllowlist
const std::vector< PanelAdmissionEntry > kDeferredDeclarations
const PanelAdmissionEntry * FindFirstClassPanelEntry(const std::string &window_id)
Returns the allowlist entry for a base window id, or nullptr.
std::string FormatPanelAdmissionFindings(const std::vector< PanelAdmissionFinding > &findings)
Joins findings into one message for test output.
const std::vector< PanelAdmissionEntry > & FirstClassPanelAllowlist()
bool IsPolicyDeclarationDeferred(const std::string &window_id)
std::vector< PanelAdmissionFinding > AuditPanelAdmission(const WorkspaceWindowManager &window_manager, size_t session_id)
Audits every window registered for session_id. Empty means admitted.
std::vector< std::string > CollectDefaultVisibleWindowIds(const WorkspaceWindowManager &window_manager, size_t session_id)
const char * WindowPresentationRoleName(WindowPresentationRole role)
const std::vector< PanelAdmissionEntry > & DeferredPolicyDeclarations()
Admission rules for registered workspace windows.
One audit failure. fix tells the panel author what to change.
Metadata for a dockable editor window (formerly PanelInfo)