Thank you for being patient! We're working hard on resolving the issue
Parent plugins host child providers in a shared layout. The built-in calendar and timeline rows are the main examples.
A parent plugin:
row.children()handle.pluginManagerThe parent owns layout, stacking, and menu positioning. The child owns item selection and item rendering.
Daily and weekly calendar layouts usually collect contentProviders.calendar:
bindCell(
$cell: CalendarSheetColumn,
{ sheet, row, handle }: BindCellContext,
): void {
const subcolumns = this.getSubcolumns(sheet, row, handle.pluginManager);
$cell.bind({ subcolumns });
}
private getSubcolumns(
sheet: SheetViewModel.Readable,
row: SheetRow.Joined.Client,
pluginManager: SheetDelegate.PluginManager,
): CalendarSubcolumn<unknown>[] {
const result: CalendarSubcolumn<unknown>[] = [];
for (const child of row.children()) {
const plugin = pluginManager.getPlugin(child.type);
const provider = plugin.contentProviders?.calendar;
if (!provider) continue;
result.push(provider.subcolumnFromRow(sheet, child, row));
}
return result;
}
This is the key extension point for SDK consumers: adding a new child row type usually means adding a new child plugin, not editing the parent layout.
Calendar parents often need full-day items from their children. That comes from
fullDayEventsFromRow() on the child's calendar provider:
private fullDayItems(
row: SheetRow.Joined.Client,
range: NaiveDate.Range,
pluginManager: SheetDelegate.PluginManager,
): EventItemFullDay[] {
const deduped = new Map<string, EventItemFullDay>();
for (const child of row.children()) {
const plugin = pluginManager.getPlugin(child.type);
const provider = plugin.contentProviders?.calendar;
const items = provider?.fullDayEventsFromRow?.(child, range) ?? [];
for (const item of items) {
deduped.set(item.inner.id, item);
}
}
return Array.from(deduped.values());
}
This lets calendar-list, tasks, and other child types contribute to the
same all-day surface.
Timeline parents do the same thing with contentProviders.timeline:
const providers: TimelineContentProvider.Provider<unknown>[] = [];
for (const child of row.children()) {
const plugin = handle.pluginManager.getPlugin(child.type);
const provider = plugin.contentProviders?.timeline?.subcolumnFromRow(
child,
row,
);
if (provider) providers.push(provider);
}
Each returned provider supplies items(), dateRange(), itemId(),
makeCell(), and bindCell() so the parent can stack and render spans.
Because the parent owns the coordinate system, it also owns the positioning handles passed into child-facing UI:
handle.popover.reveal(
"task-details",
$details,
{ x: column.withSubindex(0), y: row.id },
{ offsetY: 48 },
);
handle.fixedChild.reveal(
"timeline-span",
$span,
{ x: column.withSubindex(0), y: row.id },
{ inset: { left: 0, top: 4 }, widthBehavior: "fill" },
);
This is why parent plugins are about more than rendering. They are also the boundary between item data and on-screen coordinates.
To make a new child row participate in an existing parent layout:
RenderPlugin for the child row typecontentProviders.calendar or contentProviders.timelineThe parent layout code usually does not need to change.
RenderPlugin responsibilities