mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-10 15:36:53 +02:00
202 lines
5.2 KiB
Markdown
202 lines
5.2 KiB
Markdown
# Fix: OPEN Command Reconstruction
|
|
|
|
**Date:** 2025-12-13
|
|
**Issue:** OPEN command sometimes didn't include event bech32 when clicking edit
|
|
**Status:** ✅ Fixed
|
|
|
|
## Problem Analysis
|
|
|
|
### Root Cause
|
|
|
|
The command reconstructor was checking for the wrong props structure.
|
|
|
|
**Incorrect code:**
|
|
```typescript
|
|
case "open": {
|
|
if (props.id) { ... } // ❌ Wrong!
|
|
if (props.address) { ... } // ❌ Wrong!
|
|
return "open";
|
|
}
|
|
```
|
|
|
|
**Actual props structure:**
|
|
```typescript
|
|
{
|
|
pointer: EventPointer | AddressPointer
|
|
}
|
|
```
|
|
|
|
Where:
|
|
- `EventPointer`: `{ id: string, relays?: string[], author?: string }`
|
|
- `AddressPointer`: `{ kind: number, pubkey: string, identifier: string, relays?: string[] }`
|
|
|
|
### Why This Happened
|
|
|
|
The `parseOpenCommand` function returns `{ pointer: EventPointer | AddressPointer }`, but the reconstructor was looking for `props.id` and `props.address` directly. This mismatch caused the reconstruction to fail and return just `"open"` without the event identifier.
|
|
|
|
## Solution
|
|
|
|
Updated the `open` case in `command-reconstructor.ts` to properly handle the pointer structure:
|
|
|
|
### Implementation
|
|
|
|
```typescript
|
|
case "open": {
|
|
// Handle pointer structure from parseOpenCommand
|
|
if (!props.pointer) return "open";
|
|
|
|
const pointer = props.pointer;
|
|
|
|
try {
|
|
// EventPointer (has id field)
|
|
if ("id" in pointer) {
|
|
// If has relays or author metadata, use nevent
|
|
if (pointer.relays?.length || pointer.author) {
|
|
const nevent = nip19.neventEncode({
|
|
id: pointer.id,
|
|
relays: pointer.relays,
|
|
author: pointer.author,
|
|
});
|
|
return `open ${nevent}`;
|
|
}
|
|
// Otherwise use simple note
|
|
const note = nip19.noteEncode(pointer.id);
|
|
return `open ${note}`;
|
|
}
|
|
|
|
// AddressPointer (has kind, pubkey, identifier)
|
|
if ("kind" in pointer) {
|
|
const naddr = nip19.naddrEncode({
|
|
kind: pointer.kind,
|
|
pubkey: pointer.pubkey,
|
|
identifier: pointer.identifier,
|
|
relays: pointer.relays,
|
|
});
|
|
return `open ${naddr}`;
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to encode open command:", error);
|
|
// Fallback to raw pointer display
|
|
if ("id" in pointer) {
|
|
return `open ${pointer.id}`;
|
|
}
|
|
}
|
|
|
|
return "open";
|
|
}
|
|
```
|
|
|
|
## Encoding Strategy
|
|
|
|
### EventPointer (has `id`)
|
|
|
|
1. **With metadata** (relays or author) → Encode as `nevent`
|
|
- Input: `{ id: "abc...", relays: ["wss://relay.com"], author: "def..." }`
|
|
- Output: `open nevent1...`
|
|
- Preserves relay hints and author information
|
|
|
|
2. **Without metadata** → Encode as `note`
|
|
- Input: `{ id: "abc..." }`
|
|
- Output: `open note1...`
|
|
- Simpler, more common format
|
|
|
|
### AddressPointer (has `kind`, `pubkey`, `identifier`)
|
|
|
|
- Always encode as `naddr`
|
|
- Input: `{ kind: 30023, pubkey: "abc...", identifier: "my-article" }`
|
|
- Output: `open naddr1...`
|
|
- Used for replaceable/parameterized replaceable events
|
|
|
|
## Test Cases
|
|
|
|
### Test 1: Simple Event (note)
|
|
```typescript
|
|
// Window with EventPointer (just ID)
|
|
{
|
|
pointer: { id: "abc123..." }
|
|
}
|
|
// Reconstructs to:
|
|
"open note1..."
|
|
```
|
|
|
|
### Test 2: Event with Metadata (nevent)
|
|
```typescript
|
|
// Window with EventPointer (with relays/author)
|
|
{
|
|
pointer: {
|
|
id: "abc123...",
|
|
relays: ["wss://relay.damus.io"],
|
|
author: "def456..."
|
|
}
|
|
}
|
|
// Reconstructs to:
|
|
"open nevent1..."
|
|
```
|
|
|
|
### Test 3: Addressable Event (naddr)
|
|
```typescript
|
|
// Window with AddressPointer
|
|
{
|
|
pointer: {
|
|
kind: 30023,
|
|
pubkey: "abc123...",
|
|
identifier: "my-article",
|
|
relays: ["wss://relay.nostr.band"]
|
|
}
|
|
}
|
|
// Reconstructs to:
|
|
"open naddr1..."
|
|
```
|
|
|
|
### Test 4: Original Hex Input
|
|
```typescript
|
|
// User typed: open abc123... (hex)
|
|
// Parser creates: { pointer: { id: "abc123..." } }
|
|
// Reconstructs to: "open note1..." (encoded as bech32)
|
|
// ✅ Better UX - consistent bech32 format
|
|
```
|
|
|
|
## Why This Fix Works
|
|
|
|
1. **Correct Props Structure**: Now checks `props.pointer` instead of non-existent `props.id`
|
|
2. **Type Detection**: Uses `"id" in pointer` vs `"kind" in pointer` to distinguish types
|
|
3. **Smart Encoding**:
|
|
- Uses `nevent` when metadata exists (preserves relay hints)
|
|
- Uses `note` for simple cases (cleaner)
|
|
- Uses `naddr` for addressable events (required format)
|
|
4. **Error Handling**: Fallback to raw ID if encoding fails
|
|
5. **Consistent Output**: All reconstructed commands use bech32 format
|
|
|
|
## Impact
|
|
|
|
### Before Fix
|
|
- Clicking edit on open windows showed just `"open"` with no event ID
|
|
- Users couldn't edit open commands
|
|
- Command reconstruction was broken for all open windows
|
|
|
|
### After Fix
|
|
- ✅ Clicking edit shows full command: `open note1...` / `nevent1...` / `naddr1...`
|
|
- ✅ Users can edit and resubmit open commands
|
|
- ✅ Preserves relay hints and metadata when present
|
|
- ✅ Consistent with how users type commands
|
|
|
|
## Files Changed
|
|
|
|
- `src/lib/command-reconstructor.ts` - Fixed `open` case (lines 39-81)
|
|
|
|
## Verification
|
|
|
|
✅ TypeScript compilation successful
|
|
✅ No breaking changes
|
|
✅ Backward compatible (handles both old and new windows)
|
|
|
|
## Related Components
|
|
|
|
- `src/lib/open-parser.ts` - Defines pointer structures and parsing logic
|
|
- `src/components/EventDetailViewer.tsx` - Consumes pointer prop
|
|
- `src/types/man.ts` - Defines open command entry
|
|
|
|
## Future Improvements
|
|
|
|
None needed - the fix is complete and handles all cases properly.
|