23 KiB
Message Editor Implementation Roadmap
Status: Active Development Last Updated: 2025-11-21 Owner: Frontend/UI Team Related Docs:
docs/internal/architecture/message_system.md(Gemini's architecture vision)docs/internal/plans/message_system_improvement_plan.md(Gemini's feature proposals)
Executive Summary
This roadmap bridges Gemini's architectural vision with practical implementation steps for completing the Message Editor. The current implementation has the core foundation in place (message parsing, dictionary system, preview rendering) but lacks several key features proposed in Gemini's plan, particularly around JSON import/export, translation workflows, and theme integration.
Current State Analysis
What's Working (Completed Features)
Core Data Layer ✅
- MessageData: Full implementation with raw/parsed representations
- DictionaryEntry: Compression system with dictionary optimization
- TextElement: Command and special character parsing
- Character Encoding: Complete CharEncoder table (0x00-0x66)
- ROM Reading:
ReadAllTextData()successfully loads all 396 messages - ROM Writing:
Save()handles two-bank text data with overflow detection
Message Preview System ✅
- MessagePreview: Live rendering of messages as they appear in-game
- Font Graphics: 2BPP font tiles loaded and displayed at 0x70000
- Character Widths: Proportional font support via width table at 0x74ADF
- Preview Bitmap: Real-time message rendering with proper palette support
Editor UI ✅
- Card System: Four dockable cards (Message List, Editor, Font Atlas, Dictionary)
- Message List: Table view with ID, contents, and address columns
- Text Editor: Multiline input with live preview updates
- Command Insertion: Buttons to insert text commands and special characters
- Dictionary Display: Read-only view of all 97 dictionary entries
- Expanded Messages: Basic support for loading external message bins
Testing Coverage ✅
- Unit Tests: 20+ tests covering parsing, encoding, dictionary optimization
- Integration Tests: ROM-dependent tests verify actual game data
- Command Parsing: Regression tests for argument handling bugs
CLI Integration ✅
- Message List:
z3ed message list --format json --range 0-100 - Message Read:
z3ed message read --id 5 --format json - Message Search:
z3ed message search --query "Link" - Message Stats:
z3ed message stats --format json
What's Missing (Gaps vs. Gemini's Vision)
1. JSON Import/Export ❌ (HIGH PRIORITY)
Status: Not implemented Gemini's Vision:
[
{
"id": 0,
"address": 917504,
"text": "[W:00][SPD:00]Welcome to [D:05]...",
"context": "Uncle dying in sewers"
}
]
Current Gap:
- No
SerializeMessages()orDeserializeMessages()inMessageData - No UI for export/import operations
- No context field for translator notes
- CLI has JSON output but not JSON input
Impact: Cannot version control text, cannot use external editors, cannot collaborate with translators
2. Translation Workspace ❌ (MEDIUM PRIORITY)
Status: Not implemented Gemini's Vision: Side-by-side view with reference ROM/JSON and editable translation
Current Gap:
- No reference text display
- No side-by-side layout
- No translation progress tracking
- No language-specific dictionary optimization
Impact: Manual translation workflows are tedious and error-prone
3. Search & Replace ⚠️ (PARTIAL)
Status: Stub implementation exists Gemini's Vision: Regex support, batch replace across all messages
Current Implementation:
Find()method exists inMessageEditor(lines 574-600)- Basic UI skeleton present (search input, case sensitivity toggle)
- Missing: Replace functionality, regex support, "Find All", multi-message operations
Impact: Global text edits require manual per-message changes
4. Theme Integration ❌ (LOW PRIORITY - UI POLISH)
Status: Not implemented Current Issues:
- No hardcoded
ImVec4colors found (GOOD!) - Not using
AgentUIThemesystem for consistency - Missing semantic color names for message editor components
Impact: Message Editor UI may not match rest of application theme
5. Expanded ROM Support ⚠️ (PARTIAL)
Status: Basic implementation exists Gemini's Vision: Repointing text blocks to expanded ROM space (Banks 10+), automatic bank switching
Current Implementation:
- Can load expanded message bins (lines 322-334)
- Can save expanded messages (lines 497-508)
- Missing: Repointing logic, bank management, automatic overflow handling
Impact: Cannot support large translation projects that exceed vanilla space
6. Scripting Integration ❌ (FUTURE)
Status: Not planned Gemini's Vision: Lua/Python API for procedural text generation
Current Gap: No scripting hooks in message system
Impact: Low - nice-to-have for advanced users
Architectural Decisions Required
Decision 1: JSON Schema Design
Question: What fields should the JSON export include?
Proposal:
{
"version": "1.0",
"rom_name": "zelda3.sfc",
"messages": [
{
"id": 0,
"address": 917504,
"address_hex": "0xE0000",
"text": "[W:00][SPD:00]Welcome...",
"context": "Optional translator note",
"dictionary_optimized": true,
"expanded": false
}
],
"dictionary": [
{
"id": 0,
"token": "[D:00]",
"contents": "the"
}
]
}
Trade-offs:
- Verbose but human-readable
- Includes metadata for validation
- Context field for translator workflow
Status: ✅ RECOMMENDED
Decision 2: Translation Workspace Layout
Question: How should reference vs. translation be displayed?
Option A: Side-by-side split pane
┌────────────────┬────────────────┐
│ Reference │ Translation │
│ (English) │ (Spanish) │
│ [Read-only] │ [Editable] │
│ │ │
│ Message 0: │ Message 0: │
│ "Welcome to │ "Bienvenido a │
│ Hyrule" │ Hyrule" │
└────────────────┴────────────────┘
Option B: Top-bottom with context panel
┌────────────────────────────────┐
│ Reference: "Welcome to Hyrule" │
│ Context: Uncle's dying words │
├────────────────────────────────┤
│ Translation: │
│ [Editable text box] │
└────────────────────────────────┘
Recommendation: Option A for large screens, Option B for narrow windows
Status: ⚠️ NEEDS USER FEEDBACK
Decision 3: Dictionary Auto-Optimization
Question: Should we auto-generate optimal dictionary for new languages?
Challenges:
- Dictionary optimization is NP-hard (longest common substring problem)
- Need to preserve ROM space constraints (97 entries max)
- Different languages have different common phrases
Proposal:
- Provide "Analyze Translation" button that suggests optimal dictionary
- Let user accept/reject suggestions
- Preserve manual dictionary entries
Status: ⚠️ NEEDS RESEARCH
Implementation Priority Matrix
Phase 1: Foundation (Sprint 1-2 weeks)
Goal: JSON import/export with UI integration
Task 1.1: Implement JSON Serialization
Location: src/app/editor/message/message_data.h, message_data.cc
Priority: P0 (Blocker for translation workflow)
Estimated Effort: 3 days
Implementation:
// In MessageData
nlohmann::json SerializeToJson() const;
static absl::StatusOr<MessageData> DeserializeFromJson(const nlohmann::json& j);
// Free functions
absl::Status ExportMessagesToJson(
const std::vector<MessageData>& messages,
const std::vector<DictionaryEntry>& dictionary,
const std::string& output_path);
absl::StatusOr<std::vector<MessageData>> ImportMessagesFromJson(
const std::string& input_path);
Dependencies: nlohmann/json (already in project via CPM)
Acceptance Criteria:
- Export all 396 messages to valid JSON
- Import JSON and verify byte-for-byte ROM equivalence
- Handle malformed JSON with clear error messages
- Preserve dictionary optimization
- Include context field in schema
Task 1.2: Add Export/Import UI
Location: src/app/editor/message/message_editor.cc
Priority: P0
Estimated Effort: 2 days
UI Additions:
void MessageEditor::DrawExportImportPanel() {
if (ImGui::Button("Export to JSON")) {
std::string path = util::FileDialogWrapper::ShowSaveFileDialog("json");
PRINT_IF_ERROR(ExportMessagesToJson(list_of_texts_,
message_preview_.all_dictionaries_,
path));
}
if (ImGui::Button("Import from JSON")) {
std::string path = util::FileDialogWrapper::ShowOpenFileDialog();
auto result = ImportMessagesFromJson(path);
if (result.ok()) {
list_of_texts_ = result.value();
RefreshMessageList();
}
}
}
Acceptance Criteria:
- File dialogs open with correct filters
- Progress indicator for large exports
- Confirmation dialog on import (warns about overwriting)
- Error popup on import failure with details
Task 1.3: CLI JSON Import Support
Location: src/cli/handlers/game/message.cc
Priority: P1
Estimated Effort: 1 day
Implementation:
z3ed message import --json messages.json --rom zelda3.sfc --output zelda3_translated.sfc
Acceptance Criteria:
- Import JSON and write to ROM
- Validate JSON schema before import
- Verify ROM size constraints
- Dry-run mode (validate without writing)
Phase 2: Translation Workflow (Sprint 2-3 weeks)
Goal: Side-by-side translation UI
Task 2.1: Add Translation Mode Card
Location: src/app/editor/message/message_editor.h, message_editor.cc
Priority: P1
Estimated Effort: 5 days
New Components:
class TranslationWorkspace {
public:
void Initialize(Rom* reference_rom, Rom* translation_rom);
void DrawUI();
void LoadReferenceFromJson(const std::string& path);
private:
void DrawSideBySideView();
void DrawProgressTracker();
void UpdateTranslationProgress();
std::vector<MessageData> reference_messages_;
std::vector<MessageData> translation_messages_;
std::map<int, bool> translation_complete_flags_;
Rom* reference_rom_ = nullptr;
Rom* translation_rom_ = nullptr;
};
UI Mockup:
┌─────────────────────────────────────────────────┐
│ Translation Progress: 42/396 (10.6%) │
├──────────────────────┬──────────────────────────┤
│ Reference (EN) │ Translation (ES) │
├──────────────────────┼──────────────────────────┤
│ Message 0: │ Message 0: │
│ "Welcome to Hyrule" │ [Editable input box] │
│ │ │
│ Dictionary: [D:05] │ Dictionary: [D:05] │
├──────────────────────┴──────────────────────────┤
│ [Previous] [Next] [Mark Complete] [Skip] │
└─────────────────────────────────────────────────┘
Acceptance Criteria:
- Load reference ROM or JSON
- Display messages side-by-side
- Track translation progress (per-message completion)
- Keyboard shortcuts for navigation (Ctrl+N, Ctrl+P)
- Auto-save translated ROM on completion
Task 2.2: Context/Notes System
Location: src/app/editor/message/message_data.h
Priority: P2
Estimated Effort: 2 days
Schema Addition:
struct MessageData {
// ... existing fields ...
std::string context; // Translator notes, scene context
std::string screenshot_path; // Optional screenshot reference
nlohmann::json SerializeToJson() const {
return {
{"id", ID},
{"address", Address},
{"text", RawString},
{"context", context},
{"screenshot", screenshot_path}
};
}
};
UI Addition:
void MessageEditor::DrawContextPanel() {
ImGui::InputTextMultiline("Context Notes", ¤t_message_.context);
if (!current_message_.screenshot_path.empty()) {
ImGui::Image(LoadScreenshot(current_message_.screenshot_path));
}
}
Acceptance Criteria:
- Context field persists in JSON export/import
- Context displayed in translation workspace
- Optional screenshot attachment (stored as relative path)
Phase 3: Search & Replace (Sprint 3-1 week)
Goal: Complete Find/Replace implementation
Task 3.1: Implement Replace Functionality
Location: src/app/editor/message/message_editor.cc
Priority: P2
Estimated Effort: 2 days
Implementation:
absl::Status MessageEditor::Replace(const std::string& find_text,
const std::string& replace_text,
bool case_sensitive,
bool whole_word,
bool all_messages) {
int replaced_count = 0;
if (all_messages) {
for (auto& message : list_of_texts_) {
replaced_count += ReplaceInMessage(message, find_text, replace_text,
case_sensitive, whole_word);
}
} else {
replaced_count += ReplaceInMessage(current_message_, find_text,
replace_text, case_sensitive, whole_word);
}
return absl::OkStatus();
}
UI Updates:
void MessageEditor::DrawFindReplacePanel() {
static char find_text[256] = "";
static char replace_text[256] = "";
ImGui::InputText("Find", find_text, IM_ARRAYSIZE(find_text));
ImGui::InputText("Replace", replace_text, IM_ARRAYSIZE(replace_text));
ImGui::Checkbox("Case Sensitive", &case_sensitive_);
ImGui::Checkbox("Whole Word", &match_whole_word_);
ImGui::Checkbox("All Messages", &replace_all_messages_);
if (ImGui::Button("Replace")) {
PRINT_IF_ERROR(Replace(find_text, replace_text, case_sensitive_,
match_whole_word_, false));
}
if (ImGui::Button("Replace All")) {
PRINT_IF_ERROR(Replace(find_text, replace_text, case_sensitive_,
match_whole_word_, true));
}
}
Acceptance Criteria:
- Replace in current message
- Replace in all messages
- Case-sensitive/insensitive options
- Whole word matching
- Undo support (requires history stack)
Task 3.2: Add Regex Support
Location: src/app/editor/message/message_editor.cc
Priority: P3 (Nice-to-have)
Estimated Effort: 2 days
Implementation:
absl::Status MessageEditor::ReplaceRegex(const std::string& pattern,
const std::string& replacement,
bool all_messages) {
std::regex regex_pattern;
try {
regex_pattern = std::regex(pattern);
} catch (const std::regex_error& e) {
return absl::InvalidArgumentError(
absl::StrFormat("Invalid regex: %s", e.what()));
}
// Perform replacement...
}
Acceptance Criteria:
- Regex validation with error messages
- Capture group support ($1, $2, etc.)
- Preview matches before replacement
Phase 4: UI Polish (Sprint 4-1 week)
Goal: Theme integration and UX improvements
Task 4.1: Integrate AgentUITheme
Location: src/app/editor/message/message_editor.cc
Priority: P3
Estimated Effort: 1 day
Implementation:
void MessageEditor::DrawMessageList() {
const auto& theme = AgentUI::GetTheme();
AgentUI::PushPanelStyle();
ImGui::PushStyleColor(ImGuiCol_Header, theme.panel_bg_darker);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, theme.accent_color);
// ... table rendering ...
ImGui::PopStyleColor(2);
AgentUI::PopPanelStyle();
}
Acceptance Criteria:
- All panels use
AgentUI::PushPanelStyle() - Section headers use
AgentUI::RenderSectionHeader() - Buttons use
AgentUI::StyledButton()where appropriate - Color scheme matches rest of editor
Task 4.2: Add Keyboard Shortcuts
Location: src/app/editor/message/message_editor.cc
Priority: P3
Estimated Effort: 1 day
Shortcuts:
Ctrl+F: Open Find/ReplaceCtrl+E: Export to JSONCtrl+I: Import from JSONCtrl+S: Save ROMCtrl+N: Next message (in translation mode)Ctrl+P: Previous message (in translation mode)
Implementation:
void MessageEditor::HandleKeyboardShortcuts() {
if (ImGui::IsKeyPressed(ImGuiKey_F) &&
ImGui::GetIO().KeyCtrl) {
show_find_replace_ = true;
}
// ... other shortcuts ...
}
Acceptance Criteria:
- Shortcuts don't conflict with global shortcuts
- Shortcuts displayed in tooltips
- Configurable shortcuts (future enhancement)
Test Strategy
Unit Tests
Location: test/unit/message_data_test.cc (new file)
TEST(MessageDataTest, SerializeToJson_BasicMessage) {
MessageData msg;
msg.ID = 0;
msg.Address = 0xE0000;
msg.RawString = "Hello World";
msg.context = "Test message";
auto json = msg.SerializeToJson();
EXPECT_EQ(json["id"], 0);
EXPECT_EQ(json["text"], "Hello World");
EXPECT_EQ(json["context"], "Test message");
}
TEST(MessageDataTest, DeserializeFromJson_RoundTrip) {
MessageData original;
original.ID = 5;
original.RawString = "[W:00][K]Test";
auto json = original.SerializeToJson();
auto result = MessageData::DeserializeFromJson(json);
ASSERT_TRUE(result.ok());
EXPECT_EQ(result.value().ID, 5);
EXPECT_EQ(result.value().RawString, "[W:00][K]Test");
}
Integration Tests
Location: test/integration/message_export_test.cc (new file)
TEST_F(MessageRomTest, ExportImport_RoundTrip) {
// Export all messages to JSON
std::string json_path = "/tmp/messages.json";
EXPECT_OK(ExportMessagesToJson(list_of_texts_, dictionary_, json_path));
// Import back
auto imported = ImportMessagesFromJson(json_path);
ASSERT_TRUE(imported.ok());
// Verify identical
EXPECT_EQ(imported.value().size(), list_of_texts_.size());
for (size_t i = 0; i < list_of_texts_.size(); ++i) {
EXPECT_EQ(imported.value()[i].RawString, list_of_texts_[i].RawString);
}
}
E2E Tests
Location: test/e2e/message_editor_workflow_test.cc (new file)
TEST_F(MessageEditorE2ETest, TranslationWorkflow) {
// Open translation workspace
EXPECT_OK(ClickButton("Translation Mode"));
// Load reference ROM
EXPECT_OK(OpenFileDialog("reference_rom.sfc"));
// Navigate to message 0
EXPECT_EQ(GetCurrentMessageID(), 0);
// Edit translation
EXPECT_OK(SetTextBoxValue("Bienvenido a Hyrule"));
// Mark complete
EXPECT_OK(ClickButton("Mark Complete"));
// Verify progress
EXPECT_EQ(GetTranslationProgress(), "1/396");
}
Dependencies & Risks
External Dependencies
- nlohmann/json: Already integrated via CPM ✅
- ImGui Test Engine: Available for E2E tests ✅
- File Dialog:
util::FileDialogWrapperalready exists ✅
Technical Risks
Risk 1: JSON Schema Evolution
Impact: Breaking changes to JSON format Mitigation:
- Include version number in schema
- Implement forward/backward compatibility
- Provide migration tool for old exports
Risk 2: Dictionary Auto-Optimization Complexity
Impact: Algorithm may be too slow for real-time use Mitigation:
- Run optimization in background thread
- Provide progress indicator
- Allow cancellation
Risk 3: Large ROM Size with Expanded Messages
Impact: May exceed bank boundaries Mitigation:
- Implement repointing logic early (Phase 5)
- Warn user when approaching limits
- Suggest dictionary optimization
Success Metrics
Quantitative Metrics
- 100% message export/import success rate (no data loss)
- JSON schema supports all 396 vanilla messages
- Translation workspace reduces edit time by 50% vs. current workflow
- Search/Replace handles batch operations in <1 second
- 90%+ test coverage for new code
Qualitative Metrics
- Translator feedback: "Translation workflow is intuitive"
- No hardcoded colors in Message Editor
- UI matches yaze style guide
- Documentation complete for all new features
Timeline Estimate
| Phase | Duration | Effort (Dev Days) |
|---|---|---|
| Phase 1: JSON Export/Import | 2 weeks | 6 days |
| Phase 2: Translation Workspace | 3 weeks | 9 days |
| Phase 3: Search & Replace | 1 week | 4 days |
| Phase 4: UI Polish | 1 week | 2 days |
| Total | 7 weeks | 21 dev days |
Note: Timeline assumes single developer working full-time. Adjust for part-time work or team collaboration.
Future Enhancements (Post-MVP)
-
Scripting API (Gemini's vision)
- Expose MessageData to Lua/Python
- Allow procedural text generation
- Useful for randomizers
-
Cloud Translation Integration
- Google Translate API for quick drafts
- DeepL API for quality translations
- Requires API key management
-
Collaborative Editing
- Real-time multi-user translation
- Conflict resolution for concurrent edits
- Requires network infrastructure
-
ROM Patch Generation
- Export as
.ipsor.bpspatch files - Useful for distribution without ROM sharing
- Requires patch generation library
- Export as
-
Message Validation
- Check for overlong messages (exceeds textbox width)
- Verify all messages have terminators
- Flag unused dictionary entries
Open Questions
-
Q: Should we support multiple translation languages simultaneously? A: TBD - May require multi-ROM workspace UI
-
Q: How should we handle custom dictionary entries for expanded ROMs? A: TBD - Need research into ROM space allocation
-
Q: Should translation progress be persisted? A: TBD - Could store in
.yazeproject file -
Q: Do we need undo/redo for message editing? A: TBD - ImGui InputTextMultiline has built-in undo, may be sufficient
Conclusion
The Message Editor has a solid foundation with core parsing, preview, and UI systems in place. The main gaps are JSON export/import (P0), translation workspace (P1), and search/replace (P2).
Recommended Next Steps:
- Start with Phase 1 (JSON export/import) - this unblocks all translation workflows
- Get user feedback on translation workspace mockups before Phase 2
- Defer theme integration to Phase 4 - not blocking functionality
Estimated Effort: ~7 weeks to MVP, ~21 dev days total.
Success Criteria: Translator can export messages to JSON, edit in external tool, and re-import without data loss. Side-by-side translation workspace reduces manual comparison time by 50%.