feat: move a task between Today and Tomorrow

The app is built around two days and had no way to move anything between
them. "I won't get to this today" is the most obvious thing someone wants
from a two-day list, and the only route was deleting the task and typing
it again somewhere else.

Each row gets an arrow next to delete. On Today it sends the task forward,
on Tomorrow it points back, so one control covers both directions and the
label says which one you are getting. Alt+Right and Alt+Left do the same
from the keyboard, and each only acts in the direction that makes sense
from the day you are on.

The move itself is in src/lib/defer.js with tests, since it touches two
date keys at once and the failure modes are worth pinning down: a missing
id, the same day twice, an empty destination, and a task that somehow
already exists on the other side.
This commit is contained in:
Aculix Technologies
2026-08-16 03:25:35 +05:30
parent bfffb4ac02
commit 38af47aeb6
5 changed files with 166 additions and 7 deletions
+28
View File
@@ -0,0 +1,28 @@
/**
* Moves one task from one day to another and returns what is left on the
* source day.
*
* This is the verb a two-day app is built around: "not today, tomorrow". Both
* days are written before returning, so a caller only has to reload the day it
* is showing.
*
* A task already sitting at the destination is not duplicated. It still leaves
* the source day, which is what someone dragging a stray copy around would
* expect.
*/
export function moveTaskToDay(storage, fromKey, toKey, taskId) {
const source = storage.loadTasks(fromKey)
if (fromKey === toKey) return source
const moving = source.find(task => task.id === taskId)
if (!moving) return source
const remaining = source.filter(task => task.id !== taskId)
const destination = storage.loadTasks(toKey)
const alreadyThere = destination.some(task => task.id === taskId)
storage.saveTasks(fromKey, remaining)
if (!alreadyThere) storage.saveTasks(toKey, [...destination, moving])
return remaining
}
+83
View File
@@ -0,0 +1,83 @@
import { describe, it, expect } from 'vitest'
import { createStorage, createMemoryStore } from './storage.js'
import { moveTaskToDay } from './defer.js'
const task = (id, text = id) => ({ id, text, completed: false, createdAt: 1000 })
const setup = (seed = {}) => {
const backend = createMemoryStore()
for (const [day, tasks] of Object.entries(seed)) {
backend.setItem(`negotium-tasks-${day}`, JSON.stringify(tasks))
}
return createStorage(backend)
}
const TODAY = '2026-08-16'
const TOMORROW = '2026-08-17'
describe('moveTaskToDay', () => {
it('removes the task from the source day', () => {
const storage = setup({ [TODAY]: [task('a'), task('b')] })
moveTaskToDay(storage, TODAY, TOMORROW, 'a')
expect(storage.loadTasks(TODAY).map(t => t.id)).toEqual(['b'])
})
it('appends the task to the destination day', () => {
const storage = setup({ [TODAY]: [task('a')], [TOMORROW]: [task('existing')] })
moveTaskToDay(storage, TODAY, TOMORROW, 'a')
expect(storage.loadTasks(TOMORROW).map(t => t.id)).toEqual(['existing', 'a'])
})
it('works when the destination day has nothing stored yet', () => {
const storage = setup({ [TODAY]: [task('a')] })
moveTaskToDay(storage, TODAY, TOMORROW, 'a')
expect(storage.loadTasks(TOMORROW).map(t => t.id)).toEqual(['a'])
})
it('carries the task across unchanged', () => {
const original = { id: 'a', text: 'buy milk', completed: true, createdAt: 42 }
const storage = setup({ [TODAY]: [original] })
moveTaskToDay(storage, TODAY, TOMORROW, 'a')
expect(storage.loadTasks(TOMORROW)[0]).toEqual(original)
})
it('returns the source days remaining tasks', () => {
const storage = setup({ [TODAY]: [task('a'), task('b')] })
const result = moveTaskToDay(storage, TODAY, TOMORROW, 'a')
expect(result.map(t => t.id)).toEqual(['b'])
})
it('leaves the other tasks in their original order', () => {
const storage = setup({ [TODAY]: [task('a'), task('b'), task('c')] })
moveTaskToDay(storage, TODAY, TOMORROW, 'b')
expect(storage.loadTasks(TODAY).map(t => t.id)).toEqual(['a', 'c'])
})
it('is a no-op for an id that is not there', () => {
const storage = setup({ [TODAY]: [task('a')] })
const result = moveTaskToDay(storage, TODAY, TOMORROW, 'zzz')
expect(result.map(t => t.id)).toEqual(['a'])
expect(storage.loadTasks(TOMORROW)).toEqual([])
})
it('is a no-op when source and destination are the same day', () => {
const storage = setup({ [TODAY]: [task('a')] })
const result = moveTaskToDay(storage, TODAY, TODAY, 'a')
expect(result.map(t => t.id)).toEqual(['a'])
expect(storage.loadTasks(TODAY).map(t => t.id)).toEqual(['a'])
})
it('moves back the other way just as well', () => {
const storage = setup({ [TOMORROW]: [task('a')], [TODAY]: [task('b')] })
moveTaskToDay(storage, TOMORROW, TODAY, 'a')
expect(storage.loadTasks(TODAY).map(t => t.id)).toEqual(['b', 'a'])
expect(storage.loadTasks(TOMORROW)).toEqual([])
})
it('does not duplicate a task already present at the destination', () => {
const storage = setup({ [TODAY]: [task('a')], [TOMORROW]: [task('a')] })
moveTaskToDay(storage, TODAY, TOMORROW, 'a')
expect(storage.loadTasks(TOMORROW).map(t => t.id)).toEqual(['a'])
expect(storage.loadTasks(TODAY)).toEqual([])
})
})