Timesheets
Your week, with the empty days showing. Timesheets, in the sidebar.
Two places to write time down
Time has always been loggable on an issue, and that is still the right place when you are looking at one: you have just finished something, the issue is open in front of you, you write down what it took.
The timesheet is the other half. It answers a question the issue cannot — what have I written down this week, and what is missing — which is the question somebody actually has on a Friday afternoon with four days to remember at once.
Both write the same rows. Neither is a second system.
Not on an epic. Hours belong to the work, not to the container: an hour logged against a plan with twenty issues under it cannot be attributed to any of them, so it lands in the project's total and in nobody's estimate. An issue or a meeting, and nothing else — the picker does not list epics, the epic's own page has no clock and no plus, and the API says so with a reason.
Hours already logged against an epic are left alone: still visible on the timesheet, still correctable and still removable by whoever wrote them. Closing a door is not a reason to freeze what came through it before.
The grid is the argument
Seven columns, one per day, whether or not anything was logged on them.
An empty Wednesday is the most important thing this page displays. A list of what was recorded cannot show what was not, and what was not is exactly what somebody opened the page to find.
Each day shows what you recorded against what was expected of you:
mon 3 4h of 8h
tue 4 8h of 8h
wed 5 3h 30m of 8h
sat 8 —
The heading carries the week number after the dates — 3–9 August 2026 (week 32)
— because plenty of teams schedule, invoice and talk in week numbers, and "week
32" is something people say out loud in a way "3 August" is not.
Inside a day, durations are in hours. 1d of 8h is one quantity written twice.
Logging accepts 1w, which is five working days — forty hours, not a hundred and
sixty-eight. It reads back as 5d, because days are the largest unit anything is
displayed in: a figure somebody else has to check should carry one conversion rather
than two.
Standing work is logged like anything else. Support and maintenance are issues of the
Generic type, so their hours arrive here as ordinary entries — which is why the week still
expects the full forty however much of it is support. See
Standing work.
Where "expected" comes from
The same weekly rate the Gantt and cycles use — weekly_hours on the membership
— divided across the working week. Somebody on twenty hours is expected to record
four a day, not eight.
Deliberately borrowed rather than restated. A second copy of that arithmetic is a second thing that can drift out of step with the plan it is being compared against.
Weekends expect nothing, and a month is measured by its own working days rather than by four weeks.
What a piece of work cost
Every issue and project shows what it cost, counting people and machines in one figure. That is a number only this tracker can produce: it knows an agent spent four minutes and eighty øre while a person spent forty minutes on the same issue.
Rates are set per workspace, and per department where a department charges differently. Agent providers bill in dollars and are converted once, when the run finishes — nothing re-converts a past cost when a currency moves, so last quarter keeps the figures it was recorded with.
An unpriced hour is reported as unknown, never as free. A total that is missing
something says so, in as many words, and always travels with what it could not price. A cost
report that quietly treats people as costing nothing is the most misleading thing it could
say.
Hours worked by somebody who has since left are still counted at the workspace rate. Their work did not become free when they resigned.
Not stored per person. A per-person rate is a salary, and a tracker that stores salaries is a different product with obligations this one has not taken on.
Absence
A holiday is not the absence of a record. A week off with nothing written down looks exactly like a week somebody forgot to fill in — so the person on holiday is chased and the person who forgot is not.
Log time has a Work / Absence toggle. Absence takes a kind, hours per day, a first and last day, and an optional note, and no issue:
| Vacation | Leave you asked for and were given |
| Sick leave | Your own illness |
| Child's sick day | A child's illness, which is a separate entitlement |
| Time off in lieu | Hours worked earlier, taken back now |
| Public holiday | A day nobody worked, and nobody chose |
Five, and fixed. Sick leave and a child's sick day are different entitlements, counted separately by every employer that has to count them at all; time off in lieu says something about a previous week rather than this one; and a public holiday is nobody's choice, which is why it does not belong in the same figure as leave somebody asked for.
A week off is one entry, not five
A range, not a date. First day and last day, with the last one optional — leave it out and it is the single day it always was.
Two things it works out for itself, because somebody entering a fortnight will not check them.
A week is five days. Weekends are skipped. It is not that a Saturday is refused: nobody is expected to work one, so there is no gap to fill and no entitlement to spend.
A public holiday inside a week of leave is not leave. It is recorded — as Public holiday, not as the vacation that was asked for. Both halves of that matter:
- Recorded, because skipping it leaves exactly the hole this feature exists to close. A blank Friday reads as somebody who forgot.
- Not as vacation, because the figure deciding how much leave somebody has left should not charge them for Good Friday. That is the distinction the five kinds exist for.
So a week of vacation over Holy Week is Vacation: 3 days · Maundy Thursday, Good Friday as public holiday — and it says so in the dialog, before the button, not in a toast afterwards. The count is rarely the number of days that were picked, and a plain "Recorded" beside a figure that disagrees with the range is how somebody concludes it did not work and does it again.
A day already accounted for is left alone, and counted in the sentence. A range is the easy way to record the same Wednesday twice, and two entries of eight hours is a sixteen-hour day that nothing downstream can tell from a day somebody worked. Hours of work on a day do not block absence on it — somebody who worked the morning and went home ill is making two true statements about one day.
The holidays are Norwegian and computed rather than tabulated: ten of the twelve move with Easter, so a table would be a list that runs out, and the year it ran out is the year every range quietly started spending leave on Good Friday again. Christmas Eve and New Year's Eve stay ordinary working days — neither is a public holiday in law, and a day wrongly called one takes a day of leave back from somebody who did take it off.
A range may not exceed a year. That is a guard on a mistyped year rather than a policy about how long somebody may be away: the alternative is several thousand rows across a decade of somebody's timesheet, taken back one at a time.
Correcting an existing entry is still one entry. The range is how absence is written, not a property of a row — widening one into five would be a new decision dressed as a fix.
It fills the expectation, and it is never work
Two rules, and everything about absence follows from them.
It counts toward the day. A week's holiday makes the sheet balance — 37h 30m expected, 37h 30m recorded — so a fortnight off no longer reads as a fortnight of under-reporting.
It is not work. Absence is absent from every report about work: the time report, the cost report, the status report, the calibration, the Gantt's actual bars, and Time logged on the dashboard. A holiday cannot appear in an invoice or in a delivery figure.
Both at once means the total has to say which is which, so it does: 37h 30m recorded of 5d · of which 30h absence. One figure answering two questions would be wrong for both.
The exclusion lives in one place — the filter every report comes through — and a sweep in the test suite refuses to let a new report reach the hours table without saying whether absence counts. Five copies of that rule would be five chances for a holiday to be billed.
It reduces capacity
Absence is time that does not exist, so everything that plans against time knows about it.
- The Gantt's projected finish steps over an empty day. Two days of work starting Monday finishes Tuesday; with that week booked off, it finishes the Tuesday after
- A cycle's capacity drops by the leave inside its window, and the card says so: 40d · 20d people · 20d machines · less 5d away
- Both are counted by the hour and by the day. Half a day off leaves half a day of capacity, because a whole-day switch would either lose the other half or invent it
Standing work and absence are not the same subtraction. A support rota is time that exists and is committed; leave is time that is not there at all. A cycle counts committed time as capacity, on purpose — see Cycles — and does not count days nobody is present for.
A day never goes negative. Nine hours of leave on an eight-hour day leaves nothing, not minus one hour, or every projection after it would be meaningless rather than merely odd.
And a projection gives up rather than walking for ever: with a year booked off there is no finishing date to draw, so none is drawn.
What it is not
- Not a leave request. Nothing is approved, no balance is tracked, and nothing is deducted from an entitlement. It is a record of hours, like every other row here
- Not per-workspace. The five kinds are the same everywhere
- Not deducted from anything. Nothing tracks how much leave is left
The timer
A stopwatch on every issue, beside the spent figure. One button: either it is running on this issue or it is not.
It runs on the server, not in the tab. A timer that only exists in a browser dies with the browser — closed laptop, crashed tab, accidental reload — and takes the work it was measuring with it. The page counts the elapsed seconds itself from the start time, so it stays right through a reload and costs no requests.
The clock is in the sidebar on every page, because a timer you cannot see is a timer you forget.
Three rules about the arithmetic:
One running timer per person, enforced by a unique index rather than a check in code. Nobody works on two things at once, and a race between two tabs should be settled by the database. Starting a second stops the first, logs it, and says so — switching tasks is normal; banking time against something else silently is not.
Rounded to the nearest minute, with a floor of one. Forty seconds is still work somebody did, and rounding it to zero would throw it away quietly.
Capped at eight hours. A clock that ran for fourteen hours did not measure
fourteen hours of work; somebody went home. Recording that number would put
something indefensible into a report, and discarding it would lose an afternoon
that really happened — so the cap is logged and the note says it was capped. A
scheduled command (felagi:timers, hourly) closes anything left open, so nothing
sits running overnight.
Throwing a timer away records nothing at all, which is the right answer when it was started by accident.
It works over the API too, which is the point of a personal token — see the API reference.
Hours in a meeting
A meeting can hold hours of its own, logged from the meeting itself, and they appear on your sheet beside the hours you logged against issues. The row links back to the meeting rather than to an issue.
They are deliberately not added to the issues the meeting produced. An hour in the room and an hour doing what the room decided are different hours, and adding them together would make the meeting look like it cost nothing.
See Meetings.
Corrections
Edit or remove any entry you typed — the duration, the day it was spent on and the note — from the timesheet, from the issue it belongs to, or from the meeting it was spent in. Moving an entry from one issue to another is a correction, not a new record — both issues' totals are repaired, the one losing the hours and the one gaining them.
Correcting keeps what the entry was. A figure that came off a stopwatch stays measured however often it is fixed: the clock was running, and that is a fact about how the number was arrived at rather than about the number. Deleting an entry and typing it again would quietly turn it into a remembered one — see Where "expected" comes from for why the two are counted apart.
Over the API: PATCH /time-entries/{id}.
Totals are recalculated from the rows rather than adjusted by a difference. A sum that went wrong once stays wrong forever under a difference, and there is no way back.
Whose hours are whose
An agent's hours appear and cannot be edited. The run is in the timeline with its own duration; editing the entry would let the time report disagree with the history it was derived from. Machine time is time, and a timesheet that hid it would disagree with every report in the product.
Somebody else's hours cannot be edited either, by anybody. A workspace admin can read the whole workforce's weeks — people and agents both — and correct nobody's. Another person's hours are their statement about their own week.
Notes
A note is written in the editor, like every other piece of prose in Félagi. Formatting survives, HTML is sanitised on the way in, and the day cell and the CSV export both show the words without the markup.
Not there yet
- No approval or submission. There is no "submit for approval", no locked period and no manager sign-off. A timesheet here is a record, not a workflow — a deliberate choice, not a gap waiting to be filled.
- No timer on a Kanban card or in the palette. The issue and the sidebar.
- No billing. No rates, no billable flag, no invoice. Agent runs carry a cost in dollars from the CLI, but human hours have no price attached.
- No copying last week. Every entry is written once.
- Only your own hours over the API. You can read the whole workspace's and write only your own — see the API reference, which also covers starting and stopping the timer from an editor.
- No per-day reminder. Nothing emails you about an empty Wednesday.
- No range for work. Absence takes a first and last day; hours on an issue are one day at a time. A week on one issue is a plan, not a record of one.
- One holiday calendar, and it is Norwegian. Computed from the year rather than
configured per workspace — the same call
AbsenceKindmakes about the five kinds, for the same reason. - No leave balances. Nothing counts how much vacation somebody has left, so nothing can warn that a range spends more than they have. Recording the days correctly is what a balance would have to be built on, which is this half.