Skip to content

Map notes #2913

@Sendouc

Description

@Sendouc

Overview

Map notes let players store and share strategy notes for stage+mode combinations. Notes surface contextually in rotation views, tournament map lists, and SendouQ, and are browsable via a dedicated /notes page.

Core Concepts

Note

A note is authored by either a user or a team. Each note targets a specific stage+mode pair (both required for v1, schema allows nullable for future stage-only/mode-only/general notes).

A note contains:

  • Optional custom title (fallback: auto-generated e.g. "{Author}'s {weapon} notes", "{Team name} notes")
  • 1-4 weapon tags (shown in list views and used for filtering)
  • 1-5 tabs

Tab

Each tab within a note represents a distinct aspect (e.g. "Hold", "Retake", "Opening"). A tab contains:

  • Tab name
  • One curated lucide icon (from a fixed set of ~10-15 relevant icons)
  • Optional tldraw drawing (one per tab)
  • Optional text body (plain text for free users, markdown for supporters)
  • At least one of drawing or text is required per tab

Authorship & Permissions

Note type Who can create Who can edit Who sees it
Personal (private) Any logged-in user Author only Author only
Personal (public) Users with permission (account age gated, same as calendar events) Author only Everyone (incl. anonymous)
Team Team managers & coaches Team managers & coaches All team members
Team (public) Team managers & coaches Team managers & coaches Everyone (incl. anonymous)

Supporter-only features

  • Markdown in note text (free users get plain text)
  • Multiple notes per stage+mode (free users limited to one)

Following teams' notes

Users can follow other teams to see their public notes alongside their own. Notes you see for any stage+mode:

  1. All notes you authored
  2. All notes of teams you are a member of
  3. All public notes from teams you follow

When a user has no notes for a stage+mode, the fallback is showing popular public notes sorted by follower count descending.

Pages & Routes

All code lives in app/features/notes/.

Route Purpose
/notes Browse/search/filter all notes (your own, team, followed, public)
/notes/new Create a new note
/notes/:noteId View a note; edit if you have permission

/notes page

List view showing note metadata:

  • Title (custom or auto-generated)
  • Author (user or team name + avatar)
  • Weapon tag icons
  • Follower count (public notes)
  • Last updated

No drawing previews in the list (too heavy to render tldraw thumbnails).

Filters:

  • Stage
  • Mode
  • Weapon
  • Author type (my notes / team notes / followed / public)

/notes/new

Create form. Also reachable from /plans (new button to save a plan as a note).

/notes/:noteId

Full note view with:

  • Title, author info, stage+mode
  • Follow button with count (for team public notes)
  • Tabbed content area (tab name + lucide icon per tab)
  • tldraw drawing (view or edit mode)
  • Text content below drawing
  • Edit button (if user has permission)

Entry Points

Rotation view (front page)

One note icon overlays each rotation pair (two maps). Clicking navigates to /notes?stage=X&stage=Y&mode=Z showing notes for both maps.

If user has notes for those maps: show the note icon.
If user has no notes: still show icon, fallback to popular public notes.

Tournament map list

Note icon appears for resolved maps (after picks/bans complete), linking to /notes?stage=X&mode=Y.

SendouQ

Note icon on the current match map, linking to the relevant notes page.

Data Model

Tables

MapNote {
  id: number (PK)
  authorType: "USER" | "TEAM"
  authorId: number (userId or teamId)
  stageId: number | null (nullable for future flexibility)
  mode: ModeShort | null (nullable for future flexibility)
  title: string | null
  isPublic: boolean (default false)
  weapons: JSONColumnType<number[]> (1-4 weapon IDs)
  createdAt: Generated<number>
  updatedAt: number
}

MapNoteTab {
  id: number (PK)
  noteId: number (FK -> MapNote)
  sortOrder: number
  name: string
  icon: string (lucide icon identifier from curated set)
  drawing: JSONColumnTypeNullable<TldrawData>
  text: string | null
}

MapNoteFollow {
  userId: number (FK -> User)
  teamId: number (FK -> Team)
  createdAt: Generated<number>
  PK: (userId, teamId)
}

Indexes

  • MapNote(authorType, authorId) — fetch all notes by author
  • MapNote(stageId, mode) — fetch notes for a stage+mode
  • MapNote(isPublic) — public notes browsing
  • MapNoteFollow(userId) — teams a user follows
  • MapNoteFollow(teamId) — follower count

Open Questions

  1. tldraw storage optimization: Weapon images may be stored as blobs in tldraw JSON. Investigate saving weapon references (IDs) instead and injecting actual images at render time. Measure actual payload sizes and adjust note/tab limits accordingly.

  2. Version history for team notes: Multiple managers/coaches can edit team notes. Consider adding version history (possibly supporter-only to manage data volume). For v1, last-write-wins with "last edited by X" metadata may suffice.

Files to Create

File Purpose
app/features/notes/routes/notes.tsx Browse/search page
app/features/notes/routes/notes.new.tsx Create note page
app/features/notes/routes/notes.$noteId.tsx View/edit note page
app/features/notes/NotesRepository.server.ts Database queries
app/features/notes/notes-constants.ts Tab limit, curated icons, weapon tag limit
app/features/notes/notes-types.ts TypeScript types
app/features/notes/components/ UI components
app/features/notes/loaders/ Route loaders
app/features/notes/actions/ Route actions
New migration MapNote, MapNoteTab, MapNoteFollow tables
app/db/tables.ts Add new table types
app/routes.ts Register new routes

Files to Modify

File Change
Rotation view component Add note icon overlay on rotation pairs
Tournament match map display Add note icon for resolved maps
SendouQ match view Add note icon for current map
/plans page Add "Save as note" button

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions