Skip to content

feat(boards): allow addItem to target a specific section - #6458

Open
angusmaul wants to merge 3 commits into
homarr-labs:devfrom
angusmaul:feat/add-item-section-target
Open

feat(boards): allow addItem to target a specific section#6458
angusmaul wants to merge 3 commits into
homarr-labs:devfrom
angusmaul:feat/add-item-section-target

Conversation

@angusmaul

@angusmaul angusmaul commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Description

The board.addItem endpoint (OpenAPI POST /api/boards/items and MCP tool board_addItem) always places new items in the first empty section of a board. For boards organised into named category sections there is no API-reachable way to add an item to a specific category: addItem can't target one, and saveBoard (which can reposition items) is not exposed via OpenAPI or MCP. The only workaround is editing the database directly.

This PR adds:

  • an optional sectionId to addItemToBoardSchema. When provided, the item is placed at the next free grid position of that section (the existing placement algorithm, unchanged — just pointed at the given section). Only empty and category sections are allowed; a dynamic section id is rejected with BAD_REQUEST, and an unknown id with NOT_FOUND. When omitted, behaviour is exactly as before (first empty section), so existing OpenAPI/MCP clients are unaffected.
  • the sections of each board (id, kind, name, position) in the getAllBoards response, so API/MCP clients can discover the sectionId to pass without an additional tool (per review feedback, instead of a separate getSections procedure).
  • tests (default placement, section-targeted placement, unknown section, dynamic section, empty section id, section listing in getAllBoards).

Motivation: automation and agent clients (e.g. via the built-in MCP server) managing a categorised dashboard — "add an app tile for service X in the Local Apps category".

Verified locally: pnpm typecheck (all 37 packages) and the board router test suite (51 tests, including 6 new) pass; changed files formatted with oxfmt.

Checklist

  • Builds without warnings or errors (pnpm build, autofix with pnpm format:fix)
  • Pull request targets dev branch
  • Commits follow the conventional commits guideline
  • No shorthand variable names are used (eg. x, y, i or any abbrevation)
  • Documentation is up to date. Create a pull request here.
  • When using AI; No temp files are checked in, the code style follows the rest of the project

🤖 Generated with Claude Code

The addItem endpoint (OpenAPI POST /api/boards/items and MCP tool
board_addItem) always placed new items in the first empty section,
making it impossible for API clients to add items to named category
sections. This adds an optional sectionId to addItemToBoardSchema
which places the item at the next free grid position of the given
empty or category section, and a getSections query (OpenAPI GET
/api/boards/{boardId}/sections and MCP tool board_getSections) so
API clients can discover section ids. Without sectionId the previous
behaviour is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@angusmaul
angusmaul requested a review from a team as a code owner July 24, 2026 03:55
@dokploy-homarr-labs

Copy link
Copy Markdown

🚨 Preview Deployment Blocked - Security Protection

Your pull request was blocked from triggering preview deployments

Why was this blocked?

  • User: angusmaul
  • Repository: homarr
  • Permission Level: read
  • Required Level: write, maintain, or admin

How to resolve this:

Option 1: Get Collaborator Access (Recommended)
Ask a repository maintainer to invite you as a collaborator with write permissions or higher.

Option 2: Request Permission Override
Ask a repository administrator to disable security validation for this specific application if appropriate.

For Repository Administrators:

To disable this security check (⚠️ not recommended for public repositories):
Enter to preview settings and disable the security check.


This security measure protects against malicious code execution in preview deployments. Only trusted collaborators should have the ability to trigger deployments.

🛡️ Learn more about this security feature

This protection prevents unauthorized users from:

  • Executing malicious code on the deployment server
  • Accessing environment variables and secrets
  • Potentially compromising the infrastructure

Preview deployments are powerful but require trust. Only users with repository write access can trigger them.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The board router adds section listing and section-aware item placement. Items may target a specified section or the first empty section, with validation for missing and dynamic sections. The validation schema, placement persistence, MCP description, and tests are updated.

Changes

Board section support

Layer / File(s) Summary
Section listing
packages/api/src/router/board.ts, packages/api/src/router/test/board.spec.ts
Adds getSections, returning section metadata sorted by yOffset, enforcing view access, and reporting missing boards.
Section-aware item placement
packages/validation/src/board.ts, packages/api/src/router/board.ts, packages/api/src/router/test/board.spec.ts
Adds optional sectionId input, validates target sections, selects fallback empty sections, rejects dynamic sections, and stores layouts under the selected section with coverage for placement and errors.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant boardRouter
  participant BoardDatabase
  Client->>boardRouter: addItem(boardId, sectionId?)
  boardRouter->>BoardDatabase: resolve target section
  BoardDatabase-->>boardRouter: target section
  boardRouter->>BoardDatabase: insert item layout for target section
  boardRouter-->>Client: created item
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Yagni / Over-Engineering ✅ Passed The PR adds only the requested schema field, query, branching, and tests—no new abstractions or dependencies.
Docs Are Up To Date ✅ Passed The PR only changes board API/validation code; no apps/docs files were touched, so the widget/integration docs requirement isn’t triggered.
Title check ✅ Passed The title clearly describes the main change: allowing addItem to target a specific section.
✨ 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: 2

🧹 Nitpick comments (1)
packages/api/src/router/board.ts (1)

1452-1464: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Reorder section-authority checks to avoid duplicate board queries.

throwIfActionForbiddenAsync already performs its own boards.findFirst(..., columns: {id, creatorId, isPublic, userPermissions, groupPermissions}), so proceeding to the local board = await ctx.db.query.boards.findFirst(..., with: { sections }) double-fetches every board. Move the full-section query before that final authorization call, or pass the checked board into authorization. The NOT_FOUND contract is already consistent.

🤖 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 `@packages/api/src/router/board.ts` around lines 1452 - 1464, Reorder the board
lookup and authorization flow in the query handler containing
throwIfActionForbiddenAsync: fetch the board with its sections first, preserve
the existing NOT_FOUND response, then perform the final authorization check
without triggering a second board query, or reuse the fetched board through an
appropriate authorization API. Keep the existing view permission and board ID
checks intact.
🤖 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 `@packages/api/src/router/board.ts`:
- Around line 1436-1437: Update the MCP descriptions in
packages/api/src/router/board.ts at lines 1436-1437 and 1480-1482: document that
getSections requires view access and that boardId is obtained from
board_getAllBoards; document that addItem requires modify access and explain how
callers obtain any optional integration IDs.

In `@packages/validation/src/board.ts`:
- Line 127: Reject empty section IDs in the sectionId schema in
packages/validation/src/board.ts, while continuing to allow omitted values. In
packages/api/src/router/board.ts, update the related branching to distinguish
sectionId === undefined from provided values rather than using truthiness. Add a
regression test in packages/api/src/router/test/board.spec.ts covering
sectionId: "" and asserting it is rejected.

---

Nitpick comments:
In `@packages/api/src/router/board.ts`:
- Around line 1452-1464: Reorder the board lookup and authorization flow in the
query handler containing throwIfActionForbiddenAsync: fetch the board with its
sections first, preserve the existing NOT_FOUND response, then perform the final
authorization check without triggering a second board query, or reuse the
fetched board through an appropriate authorization API. Keep the existing view
permission and board ID checks intact.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9092f8f3-a8f1-4ba2-a472-d5207b5d2fbd

📥 Commits

Reviewing files that changed from the base of the PR and between 965aec5 and c9c111f.

📒 Files selected for processing (3)
  • packages/api/src/router/board.ts
  • packages/api/src/router/test/board.spec.ts
  • packages/validation/src/board.ts

Comment thread packages/api/src/router/board.ts Outdated
Comment thread packages/validation/src/board.ts Outdated
Reject empty sectionId at the input boundary and branch on undefined
instead of truthiness, avoid a duplicate board query in getSections by
relying on the access check for existence, and document permission
requirements and id discovery in the MCP descriptions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@ajnart ajnart left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR, I understand its goal I'm just not sure that this is how I would've gone about it. Please check my other comments

Comment thread packages/api/src/router/board.ts Outdated
Comment on lines +1509 to +1523
const targetSection =
input.sectionId !== undefined
? board.sections.find((section) => section.id === input.sectionId)
: board.sections
.filter((section) => section.kind === "empty")
.toSorted((sectionA, sectionB) => (sectionA.yOffset ?? 0) - (sectionB.yOffset ?? 0))
.at(0);

if (!targetSection) {
throw new TRPCError(
input.sectionId !== undefined
? { code: "NOT_FOUND", message: "Section not found on this board" }
: { code: "BAD_REQUEST", message: "Board has no empty section to place items in" },
);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This code looks really difficult to read with the ternaries, I'd recommend refactoring it

Comment thread packages/api/src/router/board.ts Outdated
const oldmarr = oldmarrConfigSchema.parse(JSON.parse(content));
await importOldmarrAsync(ctx.db, oldmarr, input.configuration);
}),
getSections: protectedProcedure

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see the point of adding a whole procedure for this, instead consider making it a part of the endpoint to get a dashboard ?

I think overall my MCP implementation is not the best because we have many tools instead of having a few really powerful ones. In that case I believe it's beneficial to merge this behavior into the main dashboard related query

Remove the getSections procedure and instead include the sections of
each board (id, kind, name, position) in getAllBoards, so section ids
are discoverable without an additional tool. Extract the target section
lookup of addItem into a findTargetSectionOrThrow helper to replace the
hard to read ternaries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@angusmaul

Copy link
Copy Markdown
Contributor Author

Thanks for the review @ajnart — both points addressed:

  • Dropped the getSections procedure; the sections (id, kind, name, position) are now part of the getAllBoards response, so sectionId is discoverable without an extra tool.
  • Extracted the section lookup into a findTargetSectionOrThrow helper, so the ternaries are gone.

Locally pnpm typecheck passes across all 37 packages and the board router suite is green (51 tests).

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