Skip to content

feat: track card column history for burndown charts - #180

Open
Cletus-Mccoy wants to merge 7 commits into
dkorecko:mainfrom
Cletus-Mccoy:feat/card-column-history
Open

Cletus-Mccoy wants to merge 7 commits into
dkorecko:mainfrom
Cletus-Mccoy:feat/card-column-history

Conversation

@Cletus-Mccoy

@Cletus-Mccoy Cletus-Mccoy commented Jun 20, 2026 •

Copy link
Copy Markdown

What this adds

A CardColumnHistories table that records every cross-column card move, enabling burndown charts and move timelines.

Schema (CardColumnHistory entity)

Column Type Notes
Id int PK auto-increment
CardId int FK CASCADE delete
FromColumnId int? FK SET NULL on column delete
FromColumnName string snapshot at move-time (survives column rename/delete)
ToColumnId int? FK SET NULL on column delete
ToColumnName string snapshot at move-time
MovedByUserId int? FK SET NULL on user delete
CreatedAt DateTime inherited from AbstractDbEntity — serves as the move timestamp

Where history is recorded

  • ColumnView.OnMovedFromColumn — drag-and-drop between columns
  • EditCardModal.MoveToPreviousList / MoveToNextList — modal arrow buttons

New page: /boards/{id}/history

Accessible via a fa-chart-line icon in the board header (alongside the existing stats toggle).

  • Burndown chart: SVG rendered in pure C#/Razor (no external charting library). Shows ideal vs actual open card count over time. A card is counted as "completed" on the first day it enters a column with Finished = true.
  • Recent moves: last 50 moves with card name, from → to columns, who moved it, and elapsed time.

Migration notes

Migration 20260620000001_CardColumnHistory is additive-only — no existing tables are modified. Safe to apply to an existing database.

The .Designer.cs file is omitted from this PR. Please run dotnet ef migrations add (or regenerate the designer) after merging so the model snapshot stays in sync for future migrations.

Testing

  1. Move a card between columns → verify a row appears in CardColumnHistories
  2. Navigate to /boards/{id}/history → burndown chart and recent moves render
  3. Delete a column → history rows are preserved; FromColumnId/ToColumnId are nulled, names are intact

Summary by CodeRabbit

  • New Features
    • Added card movement history tracking, recording source/destination columns, destination completion status, and who moved the card.
    • Introduced a Card history & burndown page at /boards/{Id}/history, including a burndown chart and a “Recent moves” list (up to 50).
    • Added a “Card history & burndown” navigation link in the board header for quick access.

Adds a CardColumnHistory table that records every cross-column card
move: who moved it, from/to column names + IDs, and a timestamp
(inherited from AbstractDbEntity.CreatedAt). Column names are stored
at move-time so history survives future column renames or deletes.
Nullable FKs (SetNull on column/user delete) protect history from
cascade deletion.

Hooks into both move paths:
- ColumnView.OnMovedFromColumn (drag-and-drop)
- EditCardModal.MoveToPreviousList / MoveToNextList (modal arrows)

Adds a /boards/{id}/history page with:
- An SVG burndown chart (ideal vs actual open card count over time)
  rendered in pure C#/Razor with no external charting dependency
- A recent moves timeline (last 50 moves, who/from→to/when)

Navigation: a fa-chart-line icon button is added to the board header
alongside the existing fa-chart-simple stats toggle.

Note: the migration .Designer.cs file is omitted; run
`dotnet ef migrations add` after merging to regenerate designer and
keep the snapshot current.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 20, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A new CardColumnHistory entity tracks card moves between columns. EF Core wiring, a migration, and a model snapshot update register the new table. Card move paths in EditCardModal and ColumnView now insert history records. A new /boards/{Id}/history page displays a burndown chart and a recent-moves list, linked from the BoardView toolbar.

Changes

Card History and Burndown Analytics

Layer / File(s) Summary
CardColumnHistory entity definition
Ticky.Base/Entities/CardColumnHistory.cs
Defines CardColumnHistory with required CardId, required source/target column names, optional source/target column IDs, optional MovedByUserId, and navigation properties to Card (required), FromColumn, ToColumn, and MovedBy (all optional).
EF Core registration and database schema
Ticky.Internal/Data/DataContext.cs, Ticky.Internal/Migrations/20260620000001_CardColumnHistory.cs, Ticky.Internal/Migrations/DataContextModelSnapshot.cs
Registers DbSet<CardColumnHistory> in DataContext; configures cascade delete for Card and SetNull delete behavior for optional column and user references; creates the migration defining CardColumnHistories table with columns, primary key, foreign keys, and indexes on CardId, FromColumnId, ToColumnId, and MovedByUserId; updates the model snapshot with entity and relationship configuration.
History recording in card move operations
Ticky.Web/Components/Dialogs/EditCardModal.razor, Ticky.Web/Components/Elements/ColumnView.razor
MoveToPreviousList and MoveToNextList in EditCardModal capture the source column before updating card.Column, log the move activity, and persist a CardColumnHistory record. ColumnView.OnMovedFromColumn inserts a CardColumnHistory record within the EF Core transaction after the cross-column move.
BoardHistory page with burndown and recent moves
Ticky.Web/Components/Pages/BoardHistory.razor, Ticky.Web/Components/Pages/BoardView.razor
New /boards/{Id:int}/history page loads the board, verifies user access, computes daily burndown points from card creation dates and history entries where cards move to finished columns, enriches the 50 most recent history entries with card names and mover display names, and renders a burndown SVG via RenderBurndownSvg(). BoardView adds a toolbar link to the history page.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main objective: introducing card column history tracking for burndown chart functionality, which is the primary purpose of the entire changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Ticky.Base/Entities/CardColumnHistory.cs`:
- Around line 10-12: The CardColumnHistory class captures the destination
column's identity and name but lacks a snapshot of whether that destination
column was marked as a finished/completed column at the time of the card move.
Add a boolean property (for example, ToColumnFinished) to the CardColumnHistory
class alongside the existing ToColumnId and ToColumnName properties to persist
whether the destination was a finished column at move time, enabling accurate
burndown recomputation even when column settings change or columns are deleted
later.

In `@Ticky.Web/Components/Pages/BoardHistory.razor`:
- Around line 146-173: The query starting with `var history = await
db.CardColumnHistories` materializes the entire table into memory before
filtering and taking 50 rows. To fix this, split the logic into two separate
database queries: first, use a query with `.GroupBy()` and aggregation functions
at the database level to calculate completion dates without materializing all
rows, and second, move the `.OrderByDescending(h => h.CreatedAt).Take(50)`
operation into the database query (apply it before `.ToListAsync()`) so the
database handles the sorting and limiting rather than doing it in-process. This
keeps the result sets bounded to only the data needed.
- Around line 101-107: The OnAfterRenderAsync method in the BoardHistory
component only loads data on the first render, which means stale board data
persists when navigating between different board history routes (e.g., from
`/boards/1/history` to `/boards/2/history`) since the component instance is
reused. To fix this, add a field to track the previously loaded board Id, then
in the OnAfterRenderAsync method check if the current Id route parameter has
changed from the stored value. Call LoadAsync() not only when firstRender is
true, but also when the Id parameter differs from the previously stored value,
and update the stored Id after loading to prevent unnecessary reloads.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b4e89ce6-8ea4-4244-9049-8239fa609d0e

📥 Commits

Reviewing files that changed from the base of the PR and between 971fd24 and dd5bb02.

📒 Files selected for processing (8)
  • Ticky.Base/Entities/CardColumnHistory.cs
  • Ticky.Internal/Data/DataContext.cs
  • Ticky.Internal/Migrations/20260620000001_CardColumnHistory.cs
  • Ticky.Internal/Migrations/DataContextModelSnapshot.cs
  • Ticky.Web/Components/Dialogs/EditCardModal.razor
  • Ticky.Web/Components/Elements/ColumnView.razor
  • Ticky.Web/Components/Pages/BoardHistory.razor
  • Ticky.Web/Components/Pages/BoardView.razor

Comment thread Ticky.Base/Entities/CardColumnHistory.cs Outdated
Comment thread Ticky.Web/Components/Pages/BoardHistory.razor Outdated
Comment thread Ticky.Web/Components/Pages/BoardHistory.razor Outdated
kasperdaems-svg and others added 3 commits June 20, 2026 15:03
Without the designer file the [Migration] attribute was missing,
causing EF Core to skip the migration at startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Summary bar shows Total / Open / Done card counts above the chart
- Y-axis rotated label "Open cards" added to SVG
- Legend uses inline SVG lines instead of CSS borders so dashed/solid
  styles render correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ToColumnFinished boolean snapshot to CardColumnHistory so burndown
  is accurate even if column settings change or FKs become NULL
- Switch OnAfterRenderAsync to OnParametersSetAsync so navigating
  between boards refreshes data correctly
- Push recent moves query to DB (OrderByDescending+Take(50) with Join)
  instead of materializing all history into memory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Ticky.Web/Components/Dialogs/EditCardModal.razor (1)

756-758: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Set destination index during modal arrow moves.

Line 757 and Line 858 move the card across columns without recalculating card.Index, so destination ordering can drift or collide.

💡 Suggested fix
         if (targetColumn is null)
             return;

+        var targetIndex = targetColumn.Cards.Any() ? targetColumn.Cards.Max(x => x.Index) + 1 : 0;
         var fromColumn = card.Column;
         card.Column = targetColumn;
+        card.Index = targetIndex;
         card.Activities.Add(new Activity
             {
                 Text = $"<b>moved</b> the card to <b>{targetColumn.Name}</b>",
                 UserId = _user.Id,
                 CardId = card.Id,
             });
         if (targetColumn is null)
             return;

+        var targetIndex = targetColumn.Cards.Any() ? targetColumn.Cards.Max(x => x.Index) + 1 : 0;
         var fromColumnNext = card.Column;
         card.Column = targetColumn;
+        card.Index = targetIndex;
         card.Activities.Add(new Activity
             {
                 Text = $"<b>moved</b> the card to <b>{targetColumn.Name}</b>",
                 UserId = _user.Id,
                 CardId = card.Id,
             });

Also applies to: 857-859

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Ticky.Web/Components/Dialogs/EditCardModal.razor` around lines 756 - 758,
When moving a card to a destination column by setting card.Column =
targetColumn, you must also recalculate and set the card.Index property to
maintain proper ordering within the destination column. After the line that
assigns the new column value to card.Column, determine the appropriate
destination index (typically by counting existing cards in the targetColumn or
using the position where the card should be placed) and assign it to card.Index.
Apply this fix to both locations where cards are moved across columns, around
line 757 and line 858.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Ticky.Web/Components/Pages/BoardHistory.razor`:
- Around line 178-194: The OrderByDescending(h => h.CreatedAt).Take(50)
operation in the _recentMoves query happens before the Join and GroupJoin
operations, which means SQL does not guarantee the ordering is preserved through
those join operations, resulting in unpredictable final result ordering. To fix
this, add an OrderByDescending operation at the end of the query chain, after
the Select statement that creates the new RecentMove objects, ordering by the
MovedAt property (which corresponds to the CreatedAt field) in descending order
to ensure the final results are properly sorted.

---

Outside diff comments:
In `@Ticky.Web/Components/Dialogs/EditCardModal.razor`:
- Around line 756-758: When moving a card to a destination column by setting
card.Column = targetColumn, you must also recalculate and set the card.Index
property to maintain proper ordering within the destination column. After the
line that assigns the new column value to card.Column, determine the appropriate
destination index (typically by counting existing cards in the targetColumn or
using the position where the card should be placed) and assign it to card.Index.
Apply this fix to both locations where cards are moved across columns, around
line 757 and line 858.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 439ce048-0767-43e3-b3bc-ed0c2cf8aff2

📥 Commits

Reviewing files that changed from the base of the PR and between 799a2d1 and d58d218.

📒 Files selected for processing (6)
  • Ticky.Base/Entities/CardColumnHistory.cs
  • Ticky.Internal/Migrations/20260620000001_CardColumnHistory.cs
  • Ticky.Internal/Migrations/DataContextModelSnapshot.cs
  • Ticky.Web/Components/Dialogs/EditCardModal.razor
  • Ticky.Web/Components/Elements/ColumnView.razor
  • Ticky.Web/Components/Pages/BoardHistory.razor

Comment thread Ticky.Web/Components/Pages/BoardHistory.razor Outdated
…query

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Ticky.Web/Components/Pages/BoardHistory.razor (2)

165-173: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider pre-sorting for O(n) burndown computation on large boards.

The current approach performs a linear scan of cards and completionDates.Values for each day in the range, yielding O(days × cards) complexity. For boards spanning years with hundreds of cards, this could become noticeable.

A running-sum approach with pre-sorted dates would reduce this to O(days + cards log cards).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Ticky.Web/Components/Pages/BoardHistory.razor` around lines 165 - 173, The
_burndownPoints calculation currently iterates through all cards and completion
dates for each day in the range, creating O(days × cards) complexity. Replace
this nested iteration approach by pre-sorting the created dates from cards and
completion dates before the loop, then use running-sum accumulators to track
cumulative created and completed counts as you iterate through each day. This
way, you only increment counters based on pre-sorted positions rather than
re-scanning all items daily, reducing complexity to O(days + cards log cards).

178-195: 🧹 Nitpick | 🔵 Trivial

Consider enabling ConfigureWarnings in development to verify the entire query translates to SQL.

The query's OrderByDescending(x => x.MovedAt) after the Select projection is a valid concern—EF Core must map MovedAt back to the underlying CreatedAt column for SQL translation. While EF Core 9.0 has improved projection tracking and should handle this, the preceding GroupJoin with FirstOrDefault() adds complexity that could trigger client-side evaluation earlier in the pipeline.

If client evaluation occurs anywhere in the chain, the final ordering would operate on the already-fetched 50 rows in memory, which is functionally correct but loses potential database optimization. Enable ConfigureWarnings in your Program.cs during development to catch unintended client evaluation and confirm the entire pipeline translates to SQL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Ticky.Web/Components/Pages/BoardHistory.razor` around lines 178 - 195, Enable
ConfigureWarnings in your EF Core configuration within Program.cs during
development to detect and verify that the entire LINQ query chain in the
GetRecentMoves section (containing the GroupJoin with FirstOrDefault followed by
Select and final OrderByDescending on MovedAt) translates fully to SQL without
triggering unintended client-side evaluation. This warning configuration will
help catch any client evaluation that might occur in the complex query pipeline
and ensure the database optimization is actually being applied as intended.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Ticky.Web/Components/Pages/BoardHistory.razor`:
- Around line 165-173: The _burndownPoints calculation currently iterates
through all cards and completion dates for each day in the range, creating
O(days × cards) complexity. Replace this nested iteration approach by
pre-sorting the created dates from cards and completion dates before the loop,
then use running-sum accumulators to track cumulative created and completed
counts as you iterate through each day. This way, you only increment counters
based on pre-sorted positions rather than re-scanning all items daily, reducing
complexity to O(days + cards log cards).
- Around line 178-195: Enable ConfigureWarnings in your EF Core configuration
within Program.cs during development to detect and verify that the entire LINQ
query chain in the GetRecentMoves section (containing the GroupJoin with
FirstOrDefault followed by Select and final OrderByDescending on MovedAt)
translates fully to SQL without triggering unintended client-side evaluation.
This warning configuration will help catch any client evaluation that might
occur in the complex query pipeline and ensure the database optimization is
actually being applied as intended.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: accfb1a8-9cd8-4a47-a5fd-c9f9853b129c

📥 Commits

Reviewing files that changed from the base of the PR and between d58d218 and 06ed813.

📒 Files selected for processing (1)
  • Ticky.Web/Components/Pages/BoardHistory.razor

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 20, 2026
@Cletus-Mccoy

Copy link
Copy Markdown
Author

Big fan of the app and using it for my homelab to track the state between my own work and that of various agents both running locally or just Claude as a sort of very persistent memory. I created also a little MCP server and stats api for the local agents & dashboards along with an .sh script to use the ansible playbook for Claude. This burndown is a nice visual addition for me and a history trail for when agents might mess up.

image

@dkorecko

dkorecko commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Hey there @Cletus-Mccoy! Sorry for the late response and thank you for the contribution. Looks nice, but one thing I'm wondering about is maybe whether instead of creating a new table for this, maybe we could just use the activity table, or even potentially this could be a part of an improved activity tracking. Do you have any ideas in that regard?

@Cletus-Mccoy

Cletus-Mccoy commented Jul 6, 2026 via email •

Copy link
Copy Markdown
Author

@Cletus-Mccoy

Copy link
Copy Markdown
Author

Right, had a proper look. Short version: I agree, this belongs in Activity but it needs a couple of things that aren't there yet.

  1. MoveToPreviousList / MoveToNextList log "moved the card to X" — destination only, no origin.
  2. Drag-and-drop (ColumnView.OnMovedFromColumn) doesn't log an activity. So moves made by dragging don't show up in the card history at all which is probably worth fixing regardless of what we decide here.

On "done": I'm not using column names for that. Column.Finished is already the app's own definition (board progress bar, the confetti, the green deadline), so the chart keys off that and nothing else. The one catch is that it's a checkbox that can be un-ticked later, which would retroactively change what every past move meant. So I'd snapshot it per move, for the same reason as the column names: so old rows still read correctly after a rename, not to identify anything by.

Which lands on giving Activity a typed payload instead of a second table:

ActivityType enum CardMoved, TitleChanged, ... (old rows -> Generic)
FromColumnId int? FK, SET NULL
FromColumnName string? snapshot
ToColumnId int? FK, SET NULL
ToColumnName string? snapshot
ToColumnFinished bool? snapshot

Text stays untouched so the card timeline renders exactly as it does now, and old rows just carry nulls. The burndown becomes Where(x => x.ActivityType == CardMoved) scoped to the board, with an index on (ActivityType, CreatedAt) added in the same migration. CardColumnHistories disappears.

No backfill is possible, so charts would only start from ship date which is the same as with the separate table.

If the shape works for you I'll redo the PR: drop the entity, extend Activity, add the missing logging in OnMovedFromColumn, repoint /boards/{id}/history.

The one thing I'd like your call on is how far to take it. Minimum is CardMoved + Generic and the burndown, nothing else touched. The fuller version threads the enum through every existing new Activity call site and adds a board-level activity feed. Activities are only visible inside a single card's modal today, and once ActivityType exists that feed is mostly just my "Recent moves" list with the filter removed. Bigger diff, but it's the improved activity tracking you were describing rather than half of it.

@Cletus-Mccoy

Cletus-Mccoy commented Aug 3, 2026 •

Copy link
Copy Markdown
Author

Slightly separate topic

Context for why I care about the history stuff: I drive Ticky from agents and from Ansible in my homelab in the form of an MCP server for the agents, a small CLI and playbook for scripted work, plus a /stats endpoint for dashboards. It's been running a flow with Ticky and Git as the two sources of truth, 300+ cards in, and it's held up well.

All of it talks straight to MySQL, because there's no HTTP API for cards. That works fine as far as the database is concerned, but it means anything the app would normally do around a write, I end up reimplementing including, to get CLI moves showing in the card timeline, a copy of your own activity text sitting in a bash script. If you reword that string, my rows quietly diverge. I also can't attribute a move to a real user, so it lands on whichever admin account the config knows about.

And I only did that for the CLI while my MCP server still writes cards with no activity row, so moves my agents make are invisible while moves my scripts make aren't. That inconsistency is mine, but it exists because the database is the only way in.

The blocker here I believe is auth: everything today is cookie-based Identity, so a machine client has nothing to present. Would you be open to some form of API token (per-user and/or scoped to a board)? If that sounds reasonable I'll open a separate PR with the token concept plus a small api/cards and api/boards/{id}/stats behind it, and keep this one focused on the move history. Happy to share the MCP server as a reference either way.

No urgency! I'm just raising this while we're already on the subject of activity tracking.

Cletus-Mccoy and others added 2 commits September 19, 2026 12:49
…umnHistory

Per review feedback, drop the separate CardColumnHistories table and store
move history on Activity with a typed payload:

- ActivityType enum (Generic, CardMoved); existing rows default to Generic
- FromColumnId/ToColumnId (FK, SET NULL on column delete) plus
  FromColumnName/ToColumnName/ToColumnFinished snapshots, so old rows read
  correctly after a column is renamed, deleted or has Finished toggled
- index on (ActivityType, CreatedAt) for board-level queries
- Activity.CardMoved() factory used by all move paths; timeline text is
  unchanged
- drag-and-drop (ColumnView.OnMovedFromColumn) now logs an activity, so
  dragged moves appear in the card timeline for the first time
- /boards/{id}/history reads from Activities; burndown maths moved to
  BurndownHelper with unit tests
- fix burndown SVG on comma-decimal locales (coordinates were rendered as
  "48,0", collapsing the chart)

Migration ActivityMovePayload is additive only and includes its Designer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Builds on the typed Activity payload:

- ActivityType extended to 33 explicit, append-only values; every activity
  call site (36 plus the CardMoved factory) now sets its type
- ActivityType is a required member, so the compiler rejects new untyped
  activities
- /boards/{id}/history: "Recent moves" becomes an "Activity" feed for the
  whole board with a type filter, "Load more" paging and links to cards;
  moves keep their from -> to rendering, other entries render like the
  card timeline
- tests: persisted enum values are stable, every value has a display name

Activity texts are unchanged, so card timelines render exactly as before.
No schema change beyond the previous commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Cletus-Mccoy

Copy link
Copy Markdown
Author

Reworked this to use the Activity table as you suggested: CardColumnHistories is gone, moves are now typed Activity rows, and drag-and-drop logs an activity too. It's split into two commits (a672c05 minimal, 71c4ec9 types all activities and adds a board activity feed) in case you only want the first.

I'm already running this in my own homelab, so no rush, review it whenever suits you. I also built the API/MCP idea on top of it in a separate branch (feat/api-mcp); happy to PR it later or keep it in my fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants