Projects: classes as entities, and the sidebar fits short windows
check / check (push) Canceled after 0s

A project is a class with facts (description, status, kind, party,
dates, links, notes) in one project.set event behind the format
feature projects. Four tools (books.add_project, update_project,
projects, project); documents and decisions link to projects; B-37:
on-spec costs are expensed as incurred, repayment is revenue, and the
unrecovered cost is never below zero (Kani). Viewer: a Projects list
and a project page with its figures, by month and by account. The
sidebar measures its footer instead of reserving a fixed 172 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-22 03:50:52 -06:00
1 parent 0495726001
commit 321b94562a
71 files changed
+7446 -217

No files matched your search

+91 -3
View File
@@ -1374,7 +1374,16 @@ Treas. Reg. §301.6109-4(a)); no party field may hold a nine-digit number,
and the full TIN lives only in the encrypted vault, outside the journal
(DECISION). W-9 facts need a 1099 role (contractor, landlord); W-4 facts
need the employee role; payroll lines that name their employee name a
party with that role.
party with that role. A project's text (its name, description, links and
notes: B-37) holds no full TIN either (`settings::line_text`,
`long_text`). A link is a web address, so its rule reads it as one
(`settings::holds_printed_tin_in_link`, DECISION of this rule, so store
listings can be kept): the printed SSN (3-2-4) and EIN (2-7) forms and a
bare nine digits standing alone are refused, but nine digits joined to a
letter or to `/ = ? & #` are an id in a path or query (an app store
listing's `.../id284882215`) and are kept. A project's name keeps the
plain rule: a nine-digit job or PO number is written by its last four,
as everywhere else in the books.
The full TIN, when the books need it for the 1099 worksheet, lives in the
TIN vault (`vault/` in the books dir, git-ignored; encrypted under a key
@@ -1570,9 +1579,13 @@ B-16; and a `settings.set` naming `books.schedule_convention`, a key an
earlier version refuses), `month_reposting` (a schedule month or a depreciation period
posted again after a reversal un-posted it; an older version would refuse
the second `schedule.posted`, and its projection ignore the second
`asset.depreciation_posted`, B-15) and `asset_basis` (an
`asset.depreciation_posted`, B-15), `asset_basis` (an
`asset.basis_set`, a new event type: an asset's basis facts with the
value at contribution and the §704(c) method, B-25). A command asks for
value at contribution and the §704(c) method, B-25) and `projects` (a
`project.set`, a new event type: a class's project facts; an
`attachment.linked` to the subject kind `project`, which an older version
would call corrupt; a `decision.recorded` with `projects`, which an older
version would drop silently: B-37). A command asks for
the feature it needs (`Store::ensure_feature`) before its first journaled
line, and only when the line it is about to write uses it, so a write
refused for waiting on the upgrade journals nothing, never an entry
@@ -2525,3 +2538,78 @@ package). **Checked:** `ledger-core` `tests/tax_capital.rs`;
(`the_k1_worksheet_reports_tax_basis_capital`), `tests/p3_seams.rs`
(`depreciation_is_one_figure_everywhere`,
`the_cpa_package_as_of_each_date_is_the_tools_figures`).
## B-37 A project is a class with its facts; its figures are its tagged lines
**Status:** DECISION (the owner, 2026-10-21: "a Projects page that treats
a class as a project ... Projects are entities too and can have name,
description, whatever other metadata you think they should have"; the
convention `link-to-projects` in the books: "whenever applicable, we link
things to [projects]"); CONVENTION (job costing, item 3); AUTHORITY (on
spec, item 4: ASC 450-30-25-1; ASC 340-40-25-5 with B-22;
`sources/fasb-asc.md`, both quoted through Deloitte's DART roadmaps and
not yet re-read in the ASC basic view: an open item)
1. **A project is a class.** Same id and name; every class is one (a
class an earlier version wrote has the default facts: active, kind not
recorded). Its facts, the few a job cost sheet carries: a
description (its scope of work), a status (`active`, `paused`,
`finished`, `abandoned`), a kind (`internal`: the company's own
product; `client`: work for a customer; `on_spec`: work done hoping to
be paid; `other`; or not recorded), a party (who it is for or with),
the dates it started and ended (an end only when finished or
abandoned, never before the start; informational: no figure filters by
them), up to eight links (web addresses, never opened) and notes. Names
are one line, unique ignoring capitals (in any script) and surrounding
space, and never digits only (a project is named by id or name, so such
a name would read as an id); no field holds a full TIN (B-26). A
change that keeps a project's own name never checks the name again, so
old books (replay never checks names) holding a duplicate can still
change either project, and a fact-only change never renames. Journaled as `class.added`, then the whole
record as `project.set` when there is more than a name (journal feature
`projects`, B-27). Replay checks only what it can't do without (the
class and the party exist), never the text rules, so a rule tightened
later never breaks books already kept. No budget, estimate, contract
value or percent complete: each is a document linked to the project, a
revenue-recognition question the books don't face, or the CPA's.
2. **Links.** A line is tagged with a project (its class); a receipt or
document is linked to a project (attachment subject `project`); a
decision names the projects it is about. Whether to link is the
company's convention; how is this rule.
3. **Figures (job costing).** For a period: the revenue and expense lines
tagged to it, dated in the period, closing entries left out (B-11),
each on its normal side; net is revenue less expenses. To date: every
such line through the day, from the first day the books keep, whatever
the project's dates. A tagged balance-sheet line counts nothing (an
asset bought for it counts as it is depreciated or expensed, tagged
then). No cost is allocated: a shared cost counts only as split across
lines when it is entered. The projects with the untagged lines
partition the P&L. By month, the period's last 240 months at most.
4. **On spec.** Work done hoping to be paid: its costs are expenses when
incurred (B-22; ASC 340-40-25-5 makes a cost of an anticipated
contract an asset only if, among its criteria, it is expected to be
recovered, which on spec it isn't). The hoped-for payment is a gain
contingency: no receivable and no income until received (ASC
450-30-25-1). A repayment is revenue tagged to the project (a
reimbursement credited to the expense it repays, tagged, comes to the
same). Its unrecovered cost, "if repaid", is expenses less revenue to
date, never below zero: a figure, never an asset.
5. **Status.** Finished and abandoned projects still take lines (a late
cost, a final invoice); `books.add_line` and `books.record_payout` say
so when one is tagged.
6. Omitted dates mean what B-30 says (`books.project`'s period); to date
is always stated as such, beside it.
**Enforced:** `ledger_core::classes::{ProjectFacts, ProjectStatus,
ProjectKind}`, `Ledger::{check_set_project, set_project,
replay_set_project, projects_of_party}`, `reports::{project_figures,
project_to_date, project_entries}`, `kernel::unrecovered_cost`;
`Store::{add_project, set_project, subject_feature}` (feature
`projects`); `books.add_project`, `books.update_project`,
`books.projects`, `books.project`; the viewer's Projects pages.
**Checked:** `ledger-core` `tests/spec_projects.rs`, `ledger-store`
`tests/projects.rs`, `ledger-mcp` `tests/spec_projects.rs`, `ledger-gui`
`tests/projects.rs`; bookspec `projects-net-is-its-tagged-lines` and
`projects-on-spec-costs-are-expenses`; Kani `unrecovered_cost_laws`;
proptests `prop_project_nets_partition_the_pnl`,
`prop_project_set_round_trips`.
+25
View File
@@ -249,3 +249,28 @@ consideration" it expects (55-37B); an agent recognizes only its fee or
commission (55-38).
→ B-17: an app seller is the principal for its app, so its revenue is
the gross price; the platform's commission is an expense.
## 450-30-25-1 (Gain contingencies)
Added 2026-10-21 for B-37 from the paragraph's text as Deloitte's DART
roadmaps quote it (originally FAS 5 ¶17(a)); not re-read in the ASC
basic view: check it there at the next reading. A contingency that might
result in a gain "usually should not be reflected in the financial
statements because to do so might be to recognize revenue before its
realization."
→ B-37 item 4: a payment hoped for from work done on spec is recorded
neither as a receivable nor as income until it is received; the
unrecovered cost is a figure, never an asset.
## 340-40-25-5 (Costs to fulfill a contract)
Added 2026-10-21 for B-37 as the DART revenue roadmap sets it out; not
re-read in the ASC basic view: check it there at the next reading. Costs
incurred to fulfill a contract (one not in another Topic's scope) are an
asset only if all three criteria are met: they relate directly to a
contract or an anticipated contract the entity can specifically
identify; they generate or enhance resources used in satisfying
performance obligations in the future; and they "are expected to be
recovered."
→ B-37 item 4 (with B-22): on-spec work has no contract it is expected
to recover its costs from, so its costs are expensed as incurred.