Beta

Facet Mixins Plan

How to turn facets into reusable behaviors (Temporal.isActive, Visibility.canEdit, etc.) so domain models gain real methods.

See Phase plan (Session mixins) and Architecture review.

Temporal

Applies to: Section, Session, Event, Assignment

Methods

  • isActive(now = new Date())
  • daysUntilStart()
  • durationMinutes()
  • Wraps Temporal facet; ensures start/end logic lives with data.

VisibilityControlled

Applies to: Section, Session, Resource, Discussion

Methods

  • canView(user)
  • canEdit(user)
  • clampVisibility(childVisibility)
  • Enforces “child ≤ parent visibility” rule centrally.

Sequential

Applies to: Section, Session, Assignment

Methods

  • previous()
  • next()
  • insertAfter(node)
  • Built from sequential facet (prevId/nextId).

Compositional

Applies to: Composition, Document, Signal (blocks)

Methods

  • addBlock(block)
  • moveBlock(id, position)
  • toSnapshot()
  • Encapsulates universal block manipulation.

SessionLinked

Applies to: Signal, Resource, Task, Idea

Methods

  • attachToSession(session)
  • detachFromSession()
  • listSessions()
  • Ensures anything linked to sessions behaves consistently.

TimelineAware

Applies to: Section, Assignment, Task

Methods

  • showOnCalendar()
  • nextDeadline()
  • isOverdue(now?)
  • Wraps temporal + visibility facets to drive calendar chips.

Publishable

Applies to: Composition, Signal, Resource

Methods

  • canPublish(user)
  • publish(options)
  • getShareLink()
  • Encapsulates checks for publish-as-resource + share modals.

Implementation sketch

Mixins wrap the raw Convex document type so we can call methods on Section/Session instances without building brand new ORM layers. This is the pattern to reference when we actually wire them up.

type Constructor<T = {}> = new (...args: any[]) => T;

function TemporalMixin<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    isActive(now = new Date()) {
      return now >= this.temporal.start && now <= this.temporal.end;
    }
  };
}

class Section extends TemporalMixin(VisibilityMixin(BaseSection)) {
  publish() {
    if (!this.canEdit(currentUser)) throw new Error('Nope');
    // ...
  }
}