Skip to content

[OpenSpec] [planix] task-quick-add #199

Description

@WilcoLouwerse

Add an inline "Add task" button to the footer of each kanban column that lets users create a task with just a title — without opening the full task creation form. Pressing Enter commits the task to the column; pressing Escape cancels.

This change also replaces the current "Board view coming soon" placeholder in ProjectBoard.vue with a real column-rendering layout and basic task cards, making it the first concrete step toward the full kanban board implementation.

Spec: openspec/changes/task-quick-add

Specs

  • kanban-board — Column layout rendering (replaces placeholder), basic task card display, and quick-add inline task creation at the column footer. The inline footer quick-add is a new interaction path distinct from the existing empty-column "+ Add task" button (which opens the full task form modal).

Tasks

Setup

  • 1. Render kanban columns in ProjectBoard.vue

    • The "Board view coming soon" NcEmptyContent is removed from the template
    • columns computed property returns columns from useObjectStore filtered to the current project, sorted by order
    • Columns render as a horizontal <div class="project-board__columns"> with one .project-board__column per column entry
    • Each column shows its title in a .project-board__column-header
    • When columns.length === 0 and not loading, an NcEmptyContent renders with text "No columns yet"
    • A loading indicator (NcLoadingIcon or similar) is shown while columnsLoading is true
  • 7. Create basic TaskCard.vue component

    • File src/components/TaskCard.vue exists with name: 'TaskCard'
    • Prop: task: Object (required)
    • Card renders task.title as the primary text
    • Card renders a priority indicator dot using Nextcloud CSS variables for color
    • Card renders task.dueDate if present — red text using var(--color-error) if the date is in the past
    • Card renders task.assignee if present (display name, or UUID last-8 as fallback)
    • All user-visible strings use t('planix', '...')
    • Card is keyboard-focusable (tabindex="0") for future navigation wiring

QuickAddTask component

  • 2. Create QuickAddTask.vue component

    • File src/components/QuickAddTask.vue exists with name: 'QuickAddTask'
    • Props: columnId: String (required), projectId: String (required)
    • Local state: active: false, draft: '', saving: false, errorMessage: ''
    • When active === false: renders an NcButton with label "Add task" and a PlusIcon
    • When active === true: renders the expanded form with <label> (visually hidden), <textarea>, optional error span, Save button, Cancel button
    • activate() sets active = true, clears errorMessage, and focuses the textarea via $nextTick
    • cancel() sets active = false, clears draft and errorMessage, and returns focus to the trigger button
    • NcButton, NcLoadingIcon are imported and registered in components: {}
    • PlusIcon is imported from vue-material-design-icons/Plus.vue
    • All user-visible strings use t('planix', '...')
  • 3. Wire keyboard handling (Enter / Escape)

    • @keydown.enter.prevent calls handleEnter(event)
    • handleEnter does nothing if draft.trim() is empty or saving === true
    • handleEnter calls submit() otherwise
    • @keydown.esc calls cancel()
    • Shift+Enter guard allows the default textarea newline behaviour and does NOT call submit()
    • Keyboard shortcuts work the same whether the user reaches them via mouse or keyboard navigation
  • 4. Connect to task creation store / API

    • submit() sets saving = true and clears errorMessage before the request
    • The API call uses axios from @nextcloud/axios (NEVER raw fetch())
    • The request POSTs to generateUrl('/apps/openregister/api/objects') with correct register, schema, and object payload
    • register and schema values are resolved from the registered object type
    • On success (HTTP 2xx): $emit('task-created', { task: response.data }), then cancel() is called
    • The full await call is wrapped in try/catch
    • On catch: errorMessage is set, saving = false (draft is NOT cleared)
  • 5. Add loading state and error feedback

    • <textarea> has :disabled="saving" binding
    • Save button has :disabled="saving || !draft.trim()" binding
    • Save button shows NcLoadingIcon and text "Saving…" when saving === true, and "Save" otherwise
    • Cancel button has :disabled="saving" binding
    • Error span with role="alert" renders between textarea and action buttons when errorMessage is set
    • .quick-add-task__error uses color: var(--color-error)

Quality

  • 6. Accessibility and i18n
    • Every user-visible string uses t('planix', '...')
    • The <textarea> has an associated <label> via matching for/id pair
    • The expanded form has role="form" and aria-label
    • The trigger NcButton has :aria-label so icon-only rendering is accessible
    • The error span has role="alert" so screen readers announce errors immediately
    • Focus management is correct: textarea receives focus on expand; trigger button receives focus on cancel/success
    • Color is never the sole error indicator — error text is always present alongside the red color

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions