I think one of the first design choices you have to make is: can preceded_by be NULL? The first item in the list has to do something with preceded_by. As a general rule making columns NOT NULL is prefereable; here I declared it NOT NULL, and the first row must reference itself as a result:
nworks=# create table todo (todo_id int primary key, task text, preceded_by int not null references todo (todo_id));
CREATE TABLE
nworks=# insert into todo values(1, 'do homework', 1);
INSERT 0 1
nworks=# insert into todo values(2, 'clean bedroom', 1);
INSERT 0 1
nworks=# insert into todo values(3, 'walk the dog', 2);
INSERT 0 1
nworks=# insert into todo values(4, 'call mom', 3);
INSERT 0 1
nworks=# select * from todo;
todo_id | task | preceded_by
---------+---------------+-------------
1 | do homework | 1
2 | clean bedroom | 1
3 | walk the dog | 2
4 | call mom | 3
(4 rows)
nworks=# update todo set preceded_by = 1 where todo_id = 4; -- call your mother first, it's more important
UPDATE 1
nworks=# select * from todo order by preceded_by;
todo_id | task | preceded_by
---------+---------------+-------------
2 | clean bedroom | 1
1 | do homework | 1
4 | call mom | 1
3 | walk the dog | 2
(4 rows)
I didn't do any operation to fix the sorting order once I updated item #4, so there's more work to do here. I don't think it's a practical solution.