architecture
Forma View Query Model
Context
Forma views are managed Markdown configuration nodes under .forma/views/*.md by convention. They render structured knowledge from repository-backed Markdown pages without turning view definitions into ordinary domain notes.
The initial design previously leaned toward collection-bound views and then a workspace-file source model. The current starter-kit baseline is broader and more semantic: a view starts from pages in the Forma read model, then filters those pages by taxonomy membership or query predicates.
Goals
- Keep view behavior explicit, file-backed, and reviewable.
- Support space views, uncatalogued file views, and graph views with one source/query model.
- Keep P0 query support small enough for robust diagnostics and GUI round-tripping.
- Leave room for future external render inputs without treating them as durable knowledge truth.
- Avoid executable scripts or arbitrary code in view definitions.
Non-Goals
- P0 does not include a text query DSL.
- P0 does not support DataviewJS-style or trusted JavaScript queries.
- P0 does not implement full-text predicates, date comparisons, reference predicates, diagnostic filters, runtime temporary query controls, saved personal view controls, or cross-space table joins.
- P0 does not make graph a global special feature outside the view system.
Current Starter-Kit Baseline
The current starter kit uses this shape:
---
kind: view
title: Notes
description: Configured table view over notes.
mode: table
display:
order: 30
source:
type: pages
taxonomy:
spaces:
- notes
table:
defaults:
column:
width: 16rem
minWidth: 10rem
maxWidth: 24rem
overflow: wrap
columns:
- field: fields.title
label: Title
link:
target: entry
overflow: truncate
- field: fields.summary
label: Summary
- field: fields.createdAt
label: Created At
sort:
- field: fields.createdAt
direction: desc
---
# Notes
<!-- forma:content -->
Rules from the current baseline:
source.typeis the canonical field name.- Ordinary projections use
source.type: pages. - The global graph view can use only
source.type: pageswith no taxonomy filter. - Taxonomy filters use a map-to-list shape, even for one term.
- Predicate and display field references use
field, nottarget. - Table columns are objects so labels and future display options can be added without changing the column shape.
table.defaults.columnmay contain the same four presentation fields and is useful for concise equal-width Tables.defaultsis the namespace for future default configuration; currently onlycolumnis defined. A valid field on a column overrides that default field; an absent or invalid column field inherits the valid default; an absent or invalid default falls back to the renderer's intrinsic behavior.- Table column
width,minWidth, andmaxWidthare optional positive length hints. A number means pixels; strings may use onlypx,rem, orem, including positive decimals, with a maximum numeric component of4096. Percentages, viewport units,ch, functions, variables, keywords, and arbitrary CSS are rejected.chis deferred because its value is ambiguous for zh-Hans and variable fonts without a proven code/date-column requirement. When both bounds use the same unit,minWidthmust not exceedmaxWidth. - Table column
overflowaccepts onlywraportruncate. When it is absent, each renderer keeps its established automatic presentation;wrapexplicitly permits multiline content andtruncaterequests a single-line ellipsis. - A Table column may declare
link.target: entry. Its non-empty ordinary-value cells open the source Page for that row.entryis the only supported explicit target in this first cut, so View definitions do not depend on Host-specific route paths or title-field inference. view.renderkeeps resolved schema references typed in every View mode:valuecarries an ordinary frontmatter value,referencecarries one{ path, title }target, andreferenceListcarries ordered{ path, title }targets. Hosts render the latter two as target-title links without separately matching raw frontmatter values against a reference map. An unresolved field remains an ordinaryvalue; workspace diagnostics remain the authority for its resolution failure.- Invalid optional Table presentation hints produce a non-blocking
view.tableColumnPresentationInvalidwarning, are omitted from normalized render output, and must not generate HTML styles, classes, or data attributes. - Effective same-unit
minWidth/maxWidthconstraints are checked after merging a column withtable.defaults.column; an inverted pair is warned and omitted without blocking the View. - Table and list sort stay view-level.
- Kanban columns may define local sort because each column is a separate result group.
sort.ordercan define explicit enum order for fields such as priority.
The current examples use binding paths such as fields.title, fields.updatedAt, and fields.status. These paths document the current starter-kit baseline only. The full runtime object model still needs a separate design pass during the backend and WebApp refactor.
Proposed Architecture
Source Before Query
View evaluation has two layers:
sourceselects candidate pages.queryfilters normalized page records derived from those pages.
Starter-kit views should use source.type: pages as the target source shape.
Taxonomy Filters
A view scoped to starter notes should filter through taxonomy membership:
source:
type: pages
taxonomy:
spaces:
- notes
This avoids making spaces a built-in runtime concept. Other taxonomies can use the same shape later.
Query AST
The query model remains a structured AST with boolean composition. The starter baseline uses field for leaf predicates:
query:
all:
- field: fields.status
op: equals
value: todo
The semantics are:
all: every child must match.any: at least one child must match; an emptyanyshould be neutral only when the query node is otherwise empty by implementation convention.not: every child must not match.- Leaf predicates use
field,op, and optionalvalue.
P0 supports these operators:
equalsincontainsexists
Additional operators can be introduced later when runtime typing and diagnostics justify them:
notEqualsnotInnotContainsintersectsbeforebeforeOrEqualafterafterOrEqual
Mode-Specific Query Use
List and table views use the top-level query as their candidate filter.
Kanban views first apply the top-level query, then evaluate kanban.columns[].query in column order. The first matching column wins. Health checks should warn about overlapping column queries and unmatched items.
Graph views use source and optional query to define the graph scope, but their rendering semantics are graph-specific. A repository-wide graph is not a cross-space table join.
Example global graph view:
---
kind: view
title: Graph
description: Workspace page relationship graph.
mode: graph
source:
type: pages
graph:
presentation:
nodes:
colorBy:
taxonomy: areas
edges:
- source: body
intent: link
label: links to
- source: fields
field: project
label: belongs to
- source: fields
field: assignees
label: assigned to
---
Graph node coloring is explicit and configuration-driven. When graph.presentation.nodes.colorBy.taxonomy is present, Core projects the matching configured term identity and presentation. A term color falls back to its taxonomy main color; unclassified and multi-term Pages remain neutral rather than receiving an inferred category. A missing taxonomy is a View diagnostic.
graph.presentation.nodes.colorBy.field is the mutually exclusive field-driven alternative. It reads a scalar frontmatter value through the standard fields.<path> target syntax. Valid #RRGGBB strings remain explicit colors; other non-empty strings, numbers, and booleans map deterministically to the Core-owned Graph palette. Missing, null, empty, list, and object values remain neutral and classify as Unclassified. Core owns this classification and color projection so RPC, WebApp, static export, and editor Hosts do not reinterpret frontmatter independently. Without colorBy, the Graph keeps Host-neutral node colors.
Graph should be opened through normal view navigation, tabs, or links. It is a view mode, not a separate global product surface. Relationship panels may show backlinks and outgoing links for the current page, but they are not the primary graph surface.
graph.edges configures which resolved references become graph edges. Wikilinks, embeds, and structured field references are all relation rules. Body edges declare at least source and intent. Field edges declare at least source and field; with source: fields, the field value is relative to the normalized fields object, such as assignees rather than fields.assignees. label is optional. A plain list of fields is not sufficient because field names such as assignees or blockedBy do not always define the edge label shown to users.
Health checks should validate that configured relation fields exist, are reference-typed according to the resolved space schema, and can produce workspace-relative target paths. view.render should include edge source kind, intent, field when present, resolved edge label, and semantic type in graph edge output.
The shared Graph renderer sizes nodes from their incoming and outgoing semantic reference count with a bounded logarithmic scale. One-hop selection still uses unique adjacent Pages, so repeated references may increase visual importance without duplicating focus neighbors.
Interfaces And Contracts
view.render should evaluate:
- view parameters;
- page source filters;
- taxonomy filters;
- normalized-page query definitions;
- sort definitions;
- display fields;
- table configuration;
- kanban column configuration;
- render mount points.
The first public release does not use a committed persistent index. The serve process can build the read model in memory and expose valid view metadata, including page-source graph views without taxonomy filters.
check should report structured diagnostics for:
- unsupported
source.type; - invalid source globs;
- invalid query fields;
- unsupported operators;
- incompatible operator/value combinations;
- missing referenced spaces, fields, or parameters when the view requires them;
- invalid kanban column queries;
- overlapping or unmatched kanban items when enough information is available.
Invalid queries should produce diagnostics instead of panics or silent misrendering.
P0 Scope
P0 should implement the starter-kit baseline:
source.type: pages;- taxonomy filters using map-to-list values;
query.all;query.any;query.not;fieldreferences over the currently supported starter bindings;- operators
equals,in,contains, andexists; - table, list, kanban, and graph rendering over this model;
- graph view discovery without making graph a special built-in route.
Later Scope
Later versions may add:
- text query DSL compiled into the same AST;
- reference-aware query targets;
- full-text search predicates;
- date and datetime comparisons;
- runtime temporary filters and advanced table controls;
- saved personal view controls;
- embedded view parameters;
- external source kinds;
- write-capable kanban actions.