Skip to content

feat(cfl): establish the same presenter-boundary architecture as jtk #271

@rianjs

Description

@rianjs

Summary

Establish the same presenter-boundary architecture for cfl that we just completed for jtk.

This issue is the architectural north star for the cfl side of the repo. The goal is not a partial cleanup or a few localized formatter fixes. The goal is a complete migration of cfl default text output onto the same layered model:

domain/API data
-> domain-side projection helpers when needed
-> presenter
-> OutputModel
-> shared pure renderer
-> command writes stdout/stderr

Desired End State

cfl should converge on the same rendering architecture as jtk:

  • Commands orchestrate only.
  • Presenters own domain-to-presentation mapping.
  • The shared renderer owns layout only.
  • stdout remains the primary result/artifact stream.
  • stderr remains the diagnostics/advisory/prompt stream.
  • JSON/artifact output remains separate and intentionally preserved.

By the end of this migration:

  • cfl default text output is presenter/renderer based.
  • Commands do not build user-facing strings or presenter-owned DTOs.
  • Config/env source detection lives outside presenters.
  • Shared pure rendering is the main text-rendering path for cfl.
  • Any exceptions are explicit and justified.

Architectural Requirements

1. Commands should orchestrate only

Commands should:

  • fetch data
  • branch between JSON/artifact and text output
  • call presenter
  • call shared renderer
  • write rendered stdout / stderr

Commands should not:

  • assemble display strings
  • construct presenter-owned DTOs
  • decide final section ordering for rendered output
  • embed formatting rules like suffixes, labels, or pagination wording

2. Presenters should own domain -> presentation mapping

Presenters should decide:

  • which fields are shown
  • labels and ordering
  • whether output is detail/table/message/composite
  • empty-state messages
  • pagination/advisory messages
  • status/mutation wording
  • stream destination for commentary vs primary output

3. Renderer should own layout only

The shared pure renderer should remain responsible for:

  • agent/human style layout
  • table formatting
  • detail formatting
  • message formatting
  • stdout/stderr split according to section metadata

The renderer should not:

  • inspect domain types
  • normalize domain-specific content ad hoc
  • repair string formatting mistakes coming from commands

4. Preserve CLI stream semantics

For cfl, keep the same stream contract:

  • stdout = primary result/artifact
  • stderr = warnings, diagnostics, advisory text, progress, confirmation prompts

This matters for both humans and agents.

5. Preserve JSON / artifact contracts

This migration is about default text output.

Do not silently change the JSON/artifact pathway unless there is a separate explicit product decision to do so.

Scope

Migrate all cfl default text output paths to the presenter boundary architecture.

Likely explicit exception:

  • tools/cfl/internal/cmd/init/init.go

If init is intentionally exempted for the same reason as jtk init, that should be called out explicitly in the implementation plan.

Interactive confirmation prompts may remain direct stderr prompt IO unless we intentionally decide to model them too.

cfl Inventory To Plan Against

Table / list views

These should become presenter-owned table/list sections:

  • tools/cfl/internal/cmd/space/list.go
  • tools/cfl/internal/cmd/page/list.go
  • tools/cfl/internal/cmd/search/search.go
  • tools/cfl/internal/cmd/attachment/list.go

Special attention:

  • pagination / cursor hints are currently written directly to stderr
  • some “no results” paths are still raw text instead of modeled output

Detail / key-value views

These should become presenter-owned detail sections:

  • tools/cfl/internal/cmd/space/view.go
  • tools/cfl/internal/cmd/page/view.go
  • tools/cfl/internal/cmd/configcmd/show.go

Important:

  • config show currently does source detection and formatting in the command
  • that should move to a domain-side config projection helper plus a pure presenter

Mutation / success flows

These should become presenter-owned message/composite outputs:

  • tools/cfl/internal/cmd/space/create.go
  • tools/cfl/internal/cmd/space/update.go
  • tools/cfl/internal/cmd/space/delete.go
  • tools/cfl/internal/cmd/page/create.go
  • tools/cfl/internal/cmd/page/edit.go
  • tools/cfl/internal/cmd/page/copy.go
  • tools/cfl/internal/cmd/page/delete.go
  • tools/cfl/internal/cmd/attachment/upload.go
  • tools/cfl/internal/cmd/attachment/download.go
  • tools/cfl/internal/cmd/attachment/delete.go

These are mostly Success + key/value composite outputs today and should be modeled as single presenter-owned outcomes.

Diagnostic / composite status flows

These need explicit planning rather than command-local fmt.Fprintln output:

  • tools/cfl/internal/cmd/configcmd/test.go
  • tools/cfl/internal/cmd/configcmd/clear.go
  • pagination and cursor hints in list/search commands
  • warnings such as tools/cfl/internal/cmd/page/edit.go

Composite / tricky output paths

These should be planned as their own category:

  • tools/cfl/internal/cmd/page/view.go
  • tools/cfl/internal/cmd/configcmd/test.go
  • tools/cfl/internal/cmd/configcmd/clear.go

page view is especially important. It is not just a RenderKeyValue migration. It mixes:

  • metadata
  • converted content
  • raw vs markdown modes
  • truncated vs full output
  • content-only mode
  • web mode
  • JSON/artifact output

That path needs a real presenter design, not an ad hoc rewrite.

Required Shared / Root Changes

The implementation plan should include the root-level architecture, not just command-by-command migrations.

cfl currently still routes text output through view.View in tools/cfl/internal/cmd/root/root.go.

The migration should:

  1. Introduce one authoritative render mode at the cfl root/options layer.
  2. Derive both legacy view behavior and new present renderer style from that same mode during migration.
  3. End with cfl primary text output no longer depending on view.Table, v.Success, v.RenderKeyValue, etc.

If the architectural goal is truly “same as jtk,” then cfl should end on the shared pure renderer for text output, not on a permanent mixed path.

What The Implementation Should Add

1. tools/cfl/internal/present

Add a presenter package for cfl, parallel to jtk.

Likely presenters:

  • SpacePresenter
  • PagePresenter
  • AttachmentPresenter
  • SearchPresenter
  • ConfigPresenter

2. Domain-side config projection helpers

Config value/source resolution should live outside presenters, likely in tools/cfl/internal/config.

Do not make presenters inspect env vars or load config state.

3. Shared renderer reuse

Reuse the shared pure renderer rather than creating a second cfl-specific rendering engine.

Planning Rules

These should be treated as non-negotiable for the migration:

  • No command-local construction of presenter-owned DTOs.
  • No command-local user-facing string assembly, except explicit prompt exemptions if we keep them.
  • No command-local section ordering for final rendered output.
  • No renderer-side normalization of domain content.
  • No plan that leaves cfl half on view.* and half on presenters as the merge target.

Recommended Planning Categories

The implementation plan should group work by output shape, not only by file.

Category A: table/list

  • space list
  • page list
  • search
  • attachment list

Category B: detail

  • space view
  • config show
  • page view metadata path

Category C: mutation/composite success

  • space create/update/delete
  • page create/edit/copy/delete
  • attachment upload/download/delete

Category D: diagnostic/composite status

  • config test
  • config clear
  • list/search pagination hints
  • warning/advisory paths

Category E: explicit exceptions

Probably:

  • init
  • confirmation prompts

If any exemptions are kept, they should be explicit in the plan.

TDD Expectations

This work should be heavily test-driven, same as jtk.

Expected test structure:

  1. Presenter tests first

    • Given domain/config values, assert exact OutputModel.
  2. Renderer contract tests

    • Assert exact stdout / stderr output for representative cfl output shapes using the shared renderer.
  3. Command wiring tests

    • Assert command -> presenter -> render -> write flow.
    • Preserve JSON/artifact path tests.
  4. Exact assertions for new text contracts

    • Prefer exact string assertions over loose substring checks for newly migrated text output.

Verification Expectations

The implementation plan should include explicit verification such as:

# Legacy view text helpers should be gone from cfl commands, except explicit exemptions
rg -n '\\bv\\.(Table|Success|RenderKeyValue|RenderKeyValues|Info|Warning|Error|Println|Render)\\b' tools/cfl/internal/cmd --glob '!**/*_test.go'

# Raw command-local output text should be gone, except prompts/exemptions
rg -n 'fmt\\.F(print|printf|println)\\(opts\\.(Stdout|Stderr),\\s*"' tools/cfl/internal/cmd --glob '!**/*_test.go'

# Once presenters exist, command files should not construct presenter DTOs
# (exact grep depends on final type names/package layout)

Caution

Do not mechanically copy the jtk plan.

cfl has some different shapes and risks:

  • page view mixes metadata and content rendering
  • config source reporting is more explicit and command-local today
  • list/search pagination/cursor UX differs from jtk
  • attachment flows still format sizes in command code

The architecture should be reused, but the plan should be specific to cfl.

Success Criteria

This issue is complete when the implementation plan and eventual code leave cfl in the same architectural state as jtk:

  • presenter/renderer text pipeline for default output
  • commands reduced to orchestration
  • presenters own domain-to-presentation mapping
  • config/source projection outside presenters
  • shared pure renderer as the text path
  • explicit handling of any true exceptions

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions