Skip to content

Mixed external+note entity tab hides note Markdown behind URL-only card #1623

Closed

Description

@JoshuaRowePhantom

Summary

Opening a single entity tab for an entity typed both external and note shows the external URL links but hides the note's actual Markdown body. The tab should present both: clickable external URLs and the note's rendered content, without falling back to a dump of internal metadata. This is distinct from the completed external-only URL card (#250) and multi-type note composition (#1164/#1171).

Root Cause

features/Phantom.Workspaces/ViewModels/EntityWorkspaceTabViewModel.cs:37-48 creates the single-tab card with cardViewName: this.entityCardViewResolver.ResolveViewName(this.Entity). That resolver gives any external-typed entity precedence, regardless of its other types (features/Phantom.Workspaces/ViewModels/EntityCardViewResolver.cs:7-16):

if (!string.Equals(requestedViewName, RawViewName, System.StringComparison.Ordinal)
    && entity.IsEntityType("external"))
{
    return "external";
}
return RawViewName;

features/Phantom.Workspaces/ViewModels/EntityCardViewModel.cs:63-94 then creates ExternalCard when cardViewName == "external". The normal field editors can still be built from all non-abstract types (EntityCardViewModel.cs:581-591); the note type view declares an inline content field (features/Phantom.Workspaces.Data.Core/JsonEntities/entity-type-views/note-entity-type-view.json:7-12). But features/Phantom.Workspaces/Controls/EntityCardControl.axaml:126-130 makes these presentations mutually exclusive:

<ItemsControl ItemsSource="{Binding FieldEditors}"
              IsVisible="{Binding ExternalCard, Converter={x:Static ObjectConverters.IsNull}}" />
<ContentControl Content="{Binding ExternalCard}"
                IsVisible="{Binding ExternalCard, Converter={x:Static ObjectConverters.IsNotNull}}" />

Thus a populated ExternalCard hides the note editor entirely; the external template at features/Phantom.Workspaces/Templates/WorkspaceDataTemplates.axaml:492-506 iterates Urls only. The existing note editor is not a plain text field: tests show content.default contains a text/markdown MIME attachment with content.text, and MarkdownMimeAttachmentFieldEditorViewModel renders that text via WorkspaceMarkdownView (WorkspaceDataTemplates.axaml:349-396).

Affected Files

File Role
features/Phantom.Workspaces/ViewModels/EntityCardViewResolver.cs Selects the URL-only external presentation for mixed types.
features/Phantom.Workspaces/ViewModels/EntityCardViewModel.cs Owns URL card, lazy field-editor construction, and snapshot refresh; needs a composite presentation state.
features/Phantom.Workspaces/Controls/EntityCardControl.axaml Currently hides every field editor whenever URL card exists.
features/Phantom.Workspaces/Templates/WorkspaceDataTemplates.axaml Existing URL link and Markdown editor templates should remain reusable; single-entity tab hosts the common card at lines 35-57.
features/Phantom.Workspaces/ViewModels/EntityWorkspaceTabViewModel.cs Single-entity tab resolves the card presentation; cover this route on open/reopen.
features/Phantom.Workspaces.Tests/ExternalEntityCardViewModelTests.cs, EntityCardControlTests.cs, EntityCardViewModelTests.cs, EntityWorkspaceTabViewModelTests.cs Existing resolver, visual card, editor, and tab test homes.

Design / Fix

Keep the explicit raw request authoritative. Otherwise identify external and note before the existing external-only branch (e.g. return a dedicated external-note view name). Make the card produce its existing ExternalEntityCardViewModel URL links and the curated note content editor, and allow both controls to be visible in that composite mode. Reuse FieldEditorFactory and the note type view's inline content path / existing Markdown MIME editor; do not read an invented top-level body field, show raw metadata, duplicate the Markdown, or convert URL buttons into plain text.

if (requestedViewName == RawViewName) return RawViewName;
if (entity.IsEntityType("external") && entity.IsEntityType("note")) return "external-note";
if (entity.IsEntityType("external")) return "external";
return RawViewName;
// In the common card: construct ExternalCard for external AND external-note;
// show FieldEditors for raw AND external-note, not for external-only.

Leave the pure external URL-only behavior (#250), pure note Markdown behavior (#1171), and explicit raw view unchanged. Because the entity tab and other card hosts reuse EntityCardControl, make the composite behavior consistent wherever a non-raw card is resolved; note the entity browser and ViewEntityViewModel explicitly request raw (EntityBrowserWorkspaceTabViewModel.cs:203,365, ViewEntityViewModel.cs:39) and should not be inadvertently converted. On live snapshot update rebuild both links and realized field editors, and on reopening/reloading the tab resolve the mixed type afresh. If the type set changes while the same card is open, update presentation or recreate the card instead of retaining a stale constructor-time view name. Preserve lazy editor construction on first realization.

Expected Tests

Test Name Class What It Verifies
EntityCardViewResolver_ExternalAndNote_ReturnsCompositeViewName ExternalEntityCardViewModelTests Mixed types resolve composite while an explicit raw request still returns raw.
EntityCardControl_ExternalAndNote_RendersLinksAndNoteMarkdown EntityCardControlTests Realized common card visibly shows a clickable URL and the inline WorkspaceMarkdownView with synthetic note content, once, without generic metadata fields.
EntityCardViewModel_ExternalAndNote_SnapshotUpdateRefreshesLinksAndContent EntityCardViewModelTests Updated snapshot refreshes URL model and realized note editor without hiding either or duplicating the body.
EntityWorkspaceTabViewModel_ExternalAndNote_OpenAndReopenShowsBoth EntityWorkspaceTabViewModelTests Single-entity tab selects the mixed card initially and after reopening/reloading; pure external and pure note remain unchanged.
EntityCardViewResolver_ExternalEntity_WithRawRequested_ReturnsRaw ExternalEntityCardViewModelTests Keep existing explicit-raw regression guard.

Activity

  1. JoshuaRowePhantom commented on Oct 8, 2026

    @JoshuaRowePhantom
    OwnerAuthor

    Verification result

    Status: ❌ 1 issue(s) found
    Commits checked: b8b1adc Fix mixed external and note card presentation (#1623)

    Outstanding gaps

    • The specified single-entity-tab open/reopen test does not verify that note Markdown actually appears on either opening. EntityWorkspaceTabViewModel_ExternalAndNote_OpenAndReopenShowsBoth constructs tabs without an EntityBroker or EntityTypeViewCatalog, so its cards have no FieldEditorFactory; it asserts only view name, URL model and ShowFieldEditors, leaving FieldEditors empty. Cover the actual tab path with a factory and assert the rendered Markdown alongside the URL on initial open and after reopening/reloading. The separate visual test constructs a card directly, not through the entity tab. This is a missing described test case, not a failure of the 59 tests executed.

    Tests

    worktrees\19\scripts\run-tests.ps1 -Mode fast -TestNames EntityCardControlTests,EntityCardViewModelTests,EntityWorkspaceTabViewModelTests,ExternalEntityCardViewModelTests
    scripts\test-results.log: Executed: 59; Passed: 59; Failed: 0; Skipped: 0.
    No filesystem/Git repository layer files changed; slow suite not required.

  2. JoshuaRowePhantom commented on Oct 8, 2026

    @JoshuaRowePhantom
    OwnerAuthor

    Verification result

    Status: ✅ Verified
    Commits checked: 48020b5 Test mixed external-note tab rendering across reopen (#1623); b8b1adc Fix mixed external and note card presentation (#1623).

    Outstanding gaps

    None — all criteria satisfied. The prior missing tab-path coverage is addressed: the new test constructs the tab with an initialized broker/catalog, realizes its actual card template in a window, and checks a commanded URL button and exactly one visible Markdown view with the expected content on initial open, snapshot refresh, and reopen. It also excludes internal metadata and stale/duplicated URL or body content. Resolver, raw override, pure-type handling, lazy editor construction, and type-change behavior have targeted coverage. No prohibited test synchronization, disabled tests, unresolved TODOs, or synchronous task blocking introduced. No filesystem/Git repository layer changes; slow suite not required.

    Tests

    worktrees\19\scripts\run-tests.ps1 -Mode fast -TestNames EntityCardControlTests,EntityCardViewModelTests,EntityWorkspaceTabViewModelTests,ExternalEntityCardViewModelTests
    scripts\test-results.log: Executed: 60; Passed: 60; Failed: 0; Skipped: 0.

  3. JoshuaRowePhantom commented on Oct 9, 2026

    @JoshuaRowePhantom
    OwnerAuthor

    Checked into features via #1626 (merge 1b86a98). Hosted fast CI 37966216282 passed 8,019/8,019; full CI 37966209695 passed on Windows and Linux. #1617 and #1618 retain their separate acceptance gates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdiagnosedRoot cause identifiedverified-locallyImplementation has been verified locally

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions