User profile
Your fingerprint claims your aim.
Your profile is the record of everything you've contributed to the tree. It shows your lore rank, your earned specializations (divs), your activity history, and every topic and branch you've published. It is the structured fingerprint on the web that an agent reads before your work.
A profile reads in four layers. Identity says who you are. Standing says what the community recognizes in you. Activity shows the work behind it. Your library is the private side, visible only to you. An agent reads the same layers from Public files, without a session.
flowchart TD
ID["1. Identity<br/>header, bio, linked accounts"] --> ST["2. Standing<br/>Lore, Voting Power,<br/>tiles and divs"]
ST --> AC["3. Activity<br/>matrix and log"]
AC --> LB["4. Library<br/>bookmarks, notifications<br/>private to you"]
ST --> PF["Public files<br/>what an agent reads"]
AC --> PF
The lore rank on a profile is {tier} {score}, at /u/{handle}. Formula and tiers stay in Rank. The relevance vote is in Relevance vote. Energy is in Energy. Downloads, field names, and the noname examples are in Public files.
| Surface | Path | Who |
|---|---|---|
| Profile | /u/{handle} | Anyone |
| Bookmarks | /bookmarks | The signed-in user |
| Notifications | /notifications | The signed-in user. The list is not built yet. |
1. Identity
Identity is the part of the profile that exists before any work: a name, a handle, a short bio, and the accounts behind them.
The public handle is x_handle, then google_handle, then github_handle. The profile URL and the @handle in the header use that same value.
Header
Own profile adds the options menu. A visitor sees the same identity row without it.
| Piece | On screen | Rule |
|---|---|---|
| Avatar | Left of the name | Image, or a generated mark from the handle |
| Name | Page title | users.name, then the handle. The hover card uses display_name, then name, then the handle |
| X Premium | Beside the name | Shown when x_premium is true |
| Handle | Under the name | @{handle} |
| X | Icon | Shown when x_handle is set. Opens https://x.com/{x_handle} |
| GitHub | Icon | Shown when github_handle is set. Opens https://github.com/{github_handle} |
| Lorediv | Icon | Links to / |
| Options | Own profile | Edit bio, Settings, Delete account, Log out |
Settings opens account linking. Edit bio turns on the bio fields. Log out asks for confirmation. Delete account is in the menu; the profile header does not pass it a handler. Google can be the public handle and has no icon in this row.
Bio
| Field | Edit | Limit |
|---|---|---|
| Bio | Own profile | 200 characters. Placeholder: BIO. Share a bit about yourself. |
| Location | Own profile | Free text |
| Site | Own profile | Free text |
| Joined | Read only | {Month Year} in UTC |
Save calls auth.updateProfile with bio, location, and site. Cancel restores the saved values. A visitor sees the same fields read only. The character count is on your own profile.
Linked accounts
Settings is the linking modal. A link runs in an OAuth popup. X, Google, and GitHub are the three providers.
| Provider | Public icon | Can connect | Can disconnect | Primary |
|---|---|---|---|---|
| X (Twitter) | Yes, when linked | When x_handle is empty | No | When X is linked |
| No | When Google is not linked | Yes | When X is absent and GitHub is absent | |
| GitHub | Yes, when linked | When GitHub is not linked | Yes | When X is absent |
A vote still requires one of these sign-ins. That rule is in Rank.
Hover card
An avatar opens a card. It shows name, handle, X and GitHub when those handles exist, bio, joined date, Voting Power, Lore, divs, topic count, and branch count. Open profile goes to /u/{handle}.
2. Standing
Standing is what the community has recognized in you. Two plates sit side by side and measure different things. Lore is recognition you earned. Voting Power is energy you can spend. Casting a vote spends energy and does not raise your rank. Votes you receive add to both. Six tiles beside the plates count the work and name your divs.
Lore
Label Lore. The number is formatReputationRank of Received, for example Wizard 6.1. The bar fills the current tenth. At 9.9 the bar is full.
| Side | Text |
|---|---|
| Left | Current tier range, such as 3.2 – 5.4 |
| Right | {n} to next, or Max at 9.9 |
n is the Received points still required for the next tenth.
Title tooltip: Public rank from Received. Score 1.0–9.9 across four tiers. The link explore more... goes to /community. The tooltip table is Range and Tier for Newbie, Geek, Wizard, and GOAT. Those ranges are the tier table in Rank.
Voting Power
Label Voting Power. The number is the current balance. The bar splits the month into the remaining monthly grant, energy earned above that grant, and the empty remainder. Footer: {used} used / {votes} votes and Reset in {n}d.
Title tooltip: Energy to vote. Resets on the 1st of each UTC month. The link explore more... goes to /community.
| Event | Energy |
|---|---|
| 1st of month | +31 |
| Vote received | +1 |
| 100 votes in 48h | +130 |
| Idle day | −1% |
The bar tooltip lists remaining, earned, used this month, idle decay, the monthly grant, and the max for this month. The pool resets on the 1st UTC. A high rank does not add energy. The full pool rules are in Energy.
Tiles
Six tiles sit beside the two plates. Counts use grouped digits. Topic, branch, Received, and active-day figures are lifetime totals. The matrix below them is one UTC year.
| Tile | Number | Hint |
|---|---|---|
| Topics | Authored topics | Count of authored topics. |
| Branches | Proposed branches | Count of branches. |
| Votes | Branch and relevance votes cast | Count of branch and relevance votes cast. Owner-only. |
| Received | Net of community votes on shared work | Net points from community votes on shared content. |
| Active days | Days with a topic, a branch, or (on your own profile) a vote | Total active days since joining. |
| Last active | today, 3d through six days, otherwise Sep 8 | Latest UTC day with a topic or a branch. On your own profile a vote counts too. |
The Votes tile is on every profile. A visitor sees 0. On your own profile the count includes your cast votes, and active days include days you voted.
Last active shows — when there is no topic or branch. The hover shows the full date. On your own profile the day can be a vote.
The share on one row of work splits across its tags in order. One tag takes the whole count. Two tags split 6:3. Three tags split 6:3:1. The first tag is primary, the second secondary, the third tertiary. Authored topics contribute their relevance score. Proposed branches on someone else's topic contribute the vote count on that branch. The profile keeps the top ten tags.
3. Activity
Activity is the evidence for standing. The matrix shows when you contributed. The log lists the work itself, with filters.
Activity matrix
Title Activity. The year control runs from the join year through the current UTC year, and only appears when that span is longer than one year. The current year leaves ?year= off the URL. The count reads {n} contributions in {year}: topics, branches, and, on your own profile, votes in that year.
The grid is the UTC year. Columns are weeks. The first column is the Sunday on or before 1 January. Cells outside the year are empty. Month labels are Jan through Dec, and a label is skipped when it would sit on the column next to the previous one.
| Day total | Level |
|---|---|
| 1–2 | 1 |
| 3–5 | 2 |
| 6 or more | 3 |
The join day is marked Joined. A cell tooltip lists Joined, votes, topics, branches, and the UTC date. Vote counts appear on your own profile. A visitor's year has topics and branches.
Under the grid, a curve traces a 7-day mean of those same day totals. The plot sits on the activity plate. The area under the line fades from the accent color to clear. A vertical line marks the first day of each month. The month names stay on the grid.
Log
Three tabs. Page size is 10. The tab count is the grouped list: distinct topics, not raw branch or vote rows. Until that tab has loaded, Topics uses the topic total, Branches uses the number of topics you proposed on, and Votes uses the number of topics you voted on.
| Tab | Who | Extra filters | Note in the menu |
|---|---|---|---|
| Topics | Anyone | Sort, type, tags | |
| Branches | Anyone | Sort, type, tags, Others' topics, Own topics | The last scope stays on |
| Votes | Own profile | Sort, type, tags, Topics, Branches | Votes are only visible to you. The last kind stays on |
Sort is Newest, Oldest, or Most voted. Type is All types plus /post, /ask, /proposal, /poll, /prediction, /context, /argument, /graph. Tags are All tags plus /{slug} from that user's topics, with a filter field. Reset all restores Newest, all tags, all types, both scopes, and both kinds.
The navbar search filters this list. It does not change the tiles or the matrix. Changing the year reloads the summary and leaves the log where it is.
| Query | Written when |
|---|---|
year | A year other than the current UTC year |
tab | branches or votes. Topics omits it. #proposals opens Branches. |
page | Page greater than 1 |
sort | oldest or most-voted |
tags | A subset of tags |
type | A subset of types |
scope | own or other |
kind | topic or branch on the Votes tab |
Opening the profile loads identity, the activity summary, the first Topics page, Voting Power, and Lore. A later tab, page, sort, tag, type, or search loads that log page only.
4. Your private library
Bookmarks and notifications sit on the same tabs as the profile, and only the signed-in user sees them. Neither is a public file.
Bookmarks
/bookmarks is the signed-in user's library. A guest who opens it gets the sign-in modal. A bookmark is a private save. Saving the topic and saving one branch are separate. The number on a card is the public bookmark_count. The list itself stays off /u/{handle}.md and /u/{handle}.json. The library uses the same cards and the same page size as the log. The menu adds the note Bookmarks are only visible to you.
| Filter | Choices |
|---|---|
| Sort | Newest, Oldest, Most voted |
| Show | Others' topics, Own topics, Topics, Branches |
| Type | All types, then each topic type |
| Tags | All tags, then each tag |
The last scope stays on, and the last of Topics or Branches stays on.
Notifications
/notifications sits on the same tabs as Bookmarks. A guest who opens it gets the sign-in modal. The list is not built yet. The empty screen says Notifications coming soon, and the line under it is A daily digest of votes, replies, and updates on what you own or saved. Search does nothing here. There is no public file.
Public files
The screen is for people. These files are for agents: the same fingerprint as stable, cacheable Markdown and JSON.
The screen stays /u/{handle}. Two downloads sit beside it. The metrics file is the index. A div file is the topic list for one tag. Examples below use the handle noname.
The grid on the screen is one UTC day. The metrics file rolls the current UTC year into months and each earlier year into one total.
Requests
| Request | Returns |
|---|---|
GET /u/noname | The screen. year, tab, page, sort, tags, type, scope, and kind filter the log on the screen. |
GET /u/noname.md | Metrics markdown. The log query is ignored. |
GET /u/noname.json | Metrics JSON. The log query is ignored. |
GET /u/noname and Accept: text/markdown | The metrics markdown. |
GET /u/noname and Accept: application/json | The metrics JSON. |
GET /u/noname/div/agents.md | Topics noname authored under the tag agents. Page 1. |
GET /u/noname/div/agents.json | The same list as JSON. |
GET /u/noname/div/agents.json?page=2 | Page 2. Page 1 omits ?page=. |
GET /u/noname/div/{tag}.md | Substitute {tag} from divs[].tag into div_md. |
A missing handle is 404 with the body Not found. A tag that is empty after normalization, or longer than 40 characters, is 404. A tag that normalizes and has no topics is 200 with total 0 and an empty topics array. Normalization keeps lowercase letters and digits and drops everything else, including hyphens. GET /u/noname/div/{tag}.json therefore requests the tag tag and can be that empty 200. The value in divs[].tag is the value to put in the path.
A missing, non-numeric, or sub-1 page is page 1. A page past the end is 200. total stays the real count and topics is empty.
tab=votes is the owner’s log on the screen. It is not a public file. A cookie does not change either file.
Markdown and JSON send Cache-Control: public, s-maxage=60, stale-while-revalidate=300 and a Link header (alternate, and describedby pointing at /llms.txt). A div file also sends rel="up" to the metrics markdown. Negotiated HTML sends Vary: Accept. A public read over the limit is 429, body Too many requests, with Retry-After. The cap is 60 requests a minute per IP. If Redis is down, that same cap still applies in memory on the server. The screen for a profile, a topic, a tag, the feed, and community uses this cap before it queries the database.
Topics with status open, closed, and archived are included. There is no draft and no private topic.
Name, bio, location, site, and a div topic title are user text. Markdown writes each as one line: line breaks, control characters, bidi marks, and zero-width characters are removed, and a line that would open a heading, list, quote, or fence is escaped. A title used as a link label also drops [ and ]. JSON keeps the stored string. image is the avatar URL. Reading the file does not include fetching it. A site saved from the profile form must be an http or https URL. The file prints it as text. Rank thresholds are in Rank and on /reputation.md.
Votes cast, bookmarks, voter identities, and google_handle stay off both files. The metrics file does not list topics or branch letters.
Metrics fields
schema_version is "1" on every public file.
| Field | Meaning |
|---|---|
handle | Public handle. Order on the screen is x_handle, then google_handle, then github_handle. |
name | display_name, then name, then the handle. |
bio | Profile bio, or null. |
location | Profile location, or null. |
site | Profile site, or null. |
image | Avatar URL, or null. It is not a document. Do not fetch it while reading this file. |
joined | Account creation time, ISO UTC. |
html | Screen path, /u/noname. |
md | Metrics markdown path. |
json | Metrics JSON path. |
div_md | One pattern for every div. {tag} is the placeholder. |
div_json | JSON twin of div_md. |
x_handle | X handle, or null. |
github_handle | GitHub handle, or null. |
stats | Meaning |
|---|---|
topics | Authored topics, lifetime. |
branches | Proposed branches, each letter, lifetime. |
received | Signed vote net. The Lore plate reads this net. |
active_days | Days with a public topic or a public branch. The owner’s screen can also count days they voted. The file uses this public count. |
last_active | Latest UTC day YYYY-MM-DD with a public topic or branch, or null. A vote day stays off the file. The owner’s screen can show a later vote day. |
voting_power | Energy left to cast. A high rank does not add energy. |
lore | Lowercase tier: newbie, geek, wizard, goat. |
rank | Score from 1.0 to 9.9. On the screen the plate is {Tier} {rank}, for example Wizard 5.5. |
topics_by_type lists every type, including zeros, in this order: post, ask, proposal, poll, prediction, context, argument, graph. n is the authored count.
divs[] | Meaning |
|---|---|
tag | Stored tag. This is the only path token. There is no md or json on the row. |
topics | Authored topics that carry this tag. One topic has three tags, so it is counted in three divs. |
earned | Share from the 6:3:1 split. 0 when the tag is only an authored tag and is outside the top ten. |
Rows are sorted by topics descending, then earned descending, then tag. To download, replace {tag} in div_json. agents becomes /u/noname/div/agents.json.
earned counts authored topics and branches this user proposed on someone else’s topic. It can be above 0 when topics is 0. Those branches are not listed in the div file, and they have no profile URL. Summing topics across divs counts each authored topic three times. A full list dedupes by topic id. Summing received on div rows does not equal stats.received. stats.branches is the branches this user proposed. topics[].branches is every branch on that topic.
matrix | Meaning |
|---|---|
current_year | UTC year of the request. |
months | YYYY-MM rows for that year that have a topic or a branch. Ascending. topics and branches are counts created in that month. |
years | One total per earlier year, newest first. JSON sends [] when there is no earlier year. Markdown omits the Earlier years heading in that case. |
A year with no months renders No public topic or branch months in this year.
Metrics markdown
---
schema_version: 1
handle: noname
html: /u/noname
json: /u/noname.json
div_md: /u/noname/div/{tag}.md
div_json: /u/noname/div/{tag}.json
---
# No Name
Public notes under one handle.
Joined: 2024-03-02T00:00:00.000Z
X: @noname
## Stats
- Topics: 6
- Branches: 14
- Received: 57
- Active days: 9
- Last active: 2026-09-04
- Lore: wizard
- Rank: 5.5
- Voting power: 18
## Topics by type
- post: 2
- ask: 1
- proposal: 2
- poll: 0
- prediction: 1
- context: 0
- argument: 0
- graph: 0
## Divs
- agents — 3 topics, earned 12
- design — 2 topics, earned 6
- notes — 1 topic
## Matrix
### 2026
- 2026-02 topics=2 branches=4
- 2026-09 topics=1 branches=2
### Earlier years
- 2025 topics=3 branches=8
earned is omitted on the line when it is 0. notes is that case.
Metrics JSON
{
"schema_version": "1",
"handle": "noname",
"name": "No Name",
"bio": "Public notes under one handle.",
"location": null,
"site": null,
"image": null,
"joined": "2024-03-02T00:00:00.000Z",
"html": "/u/noname",
"md": "/u/noname.md",
"json": "/u/noname.json",
"div_md": "/u/noname/div/{tag}.md",
"div_json": "/u/noname/div/{tag}.json",
"x_handle": "noname",
"github_handle": null,
"stats": {
"topics": 6,
"branches": 14,
"received": 57,
"active_days": 9,
"last_active": "2026-09-04",
"voting_power": 18,
"lore": "wizard",
"rank": 5.5
},
"topics_by_type": [
{ "type": "post", "n": 2 },
{ "type": "ask", "n": 1 },
{ "type": "proposal", "n": 2 },
{ "type": "poll", "n": 0 },
{ "type": "prediction", "n": 1 },
{ "type": "context", "n": 0 },
{ "type": "argument", "n": 0 },
{ "type": "graph", "n": 0 }
],
"divs": [
{ "tag": "agents", "topics": 3, "earned": 12 },
{ "tag": "design", "topics": 2, "earned": 6 },
{ "tag": "notes", "topics": 1, "earned": 0 }
],
"matrix": {
"current_year": 2026,
"months": [
{ "month": "2026-02", "topics": 2, "branches": 4 },
{ "month": "2026-09", "topics": 1, "branches": 2 }
],
"years": [
{ "year": 2025, "topics": 3, "branches": 8 }
]
}
}
Div fields
GET /u/noname/div/agents.json returns the topics in that div.
| Field | Meaning |
|---|---|
tag | The div that was requested. |
profile_md | Metrics markdown for this handle. |
profile_json | Metrics JSON for this handle. |
md | This page. Page 2 appends ?page=2. |
json | JSON twin of md. |
page | Page number. The first page is 1. |
page_size | 10. |
total | Authored topics in this div. |
total_pages | ceil(total / 10), at least 1. |
prev | { "md", "json" } for the previous page, or null. |
next | { "md", "json" } for the next page, or null. |
topics | The page of topics, newest first. |
topics[] | Meaning |
|---|---|
id | Topic id. |
title | Topic title. |
path | /topics/{type}/{slug}. The topic file is path.json. |
type | post, ask, proposal, poll, prediction, context, argument, or graph. |
branches | How many branches that topic has. On the topic file, branches is the array. |
received | That topic’s relevance_score. The topic file calls the same number vote. |
Div markdown
---
schema_version: 1
handle: noname
tag: agents
page: 1
json: /u/noname/div/agents.json
profile: /u/noname.md
---
# agents
Topics by [@noname](/u/noname.md) in this div. Page 1 of 1 (3).
- [Field notes on public rank](/topics/post/field-notes-on-public-rank) (`post`) id=`00000000-0000-4000-8000-000000000001` branches=2 received=4
- [Where should the div live](/topics/ask/where-should-the-div-live) (`ask`) id=`00000000-0000-4000-8000-000000000002` branches=5 received=9
- [Ship the metrics file first](/topics/proposal/ship-the-metrics-file-first) (`proposal`) id=`00000000-0000-4000-8000-000000000003` branches=3 received=6
An empty div uses the same frontmatter and the line None.
Div JSON
{
"schema_version": "1",
"handle": "noname",
"name": "No Name",
"tag": "agents",
"profile_md": "/u/noname.md",
"profile_json": "/u/noname.json",
"md": "/u/noname/div/agents.md",
"json": "/u/noname/div/agents.json",
"page": 1,
"page_size": 10,
"total": 3,
"total_pages": 1,
"prev": null,
"next": null,
"topics": [
{
"id": "00000000-0000-4000-8000-000000000001",
"title": "Field notes on public rank",
"path": "/topics/post/field-notes-on-public-rank",
"type": "post",
"branches": 2,
"received": 4
},
{
"id": "00000000-0000-4000-8000-000000000002",
"title": "Where should the div live",
"path": "/topics/ask/where-should-the-div-live",
"type": "ask",
"branches": 5,
"received": 9
},
{
"id": "00000000-0000-4000-8000-000000000003",
"title": "Ship the metrics file first",
"path": "/topics/proposal/ship-the-metrics-file-first",
"type": "proposal",
"branches": 3,
"received": 6
}
]
}
Page 2
noname’s agents div fits on one page, so next is null. When a div has 11 topics, GET /u/noname/div/agents.json?page=2 returns one row and points back at page 1. Page 1 would set next to the ?page=2 paths. The shape is:
{
"schema_version": "1",
"handle": "noname",
"name": "No Name",
"tag": "agents",
"profile_md": "/u/noname.md",
"profile_json": "/u/noname.json",
"md": "/u/noname/div/agents.md?page=2",
"json": "/u/noname/div/agents.json?page=2",
"page": 2,
"page_size": 10,
"total": 11,
"total_pages": 2,
"prev": {
"md": "/u/noname/div/agents.md",
"json": "/u/noname/div/agents.json"
},
"next": null,
"topics": [
{
"id": "00000000-0000-4000-8000-00000000000b",
"title": "Eleventh note",
"path": "/topics/post/eleventh-note",
"type": "post",
"branches": 1,
"received": 0
}
]
}
---
schema_version: 1
handle: noname
tag: agents
page: 2
json: /u/noname/div/agents.json?page=2
profile: /u/noname.md
---
# agents
Topics by [@noname](/u/noname.md) in this div. Page 2 of 2 (11).
Previous: [/u/noname/div/agents.md](/u/noname/div/agents.md)
- [Eleventh note](/topics/post/eleventh-note) (`post`) id=`00000000-0000-4000-8000-00000000000b` branches=1 received=0
Code
| Screen | File |
|---|---|
| Profile page | apps/frontend/src/components/profile/UserProfile.tsx |
| Header and menu | apps/frontend/src/components/profile/ProfileHeader.tsx |
| Bio | apps/frontend/src/components/profile/BioCard.tsx |
| Account linking | apps/frontend/src/components/ui/AccountLinkingModal.tsx |
| Plates and matrix | apps/frontend/src/components/profile/ActivityMatrix.tsx |
| Voting Power | apps/frontend/src/components/profile/VotingPower.tsx |
| Lore | apps/frontend/src/components/profile/ReputationLevel.tsx |
| Year grid | apps/frontend/src/lib/year-grid.ts |
| Summary counts | apps/frontend/src/server/lib/profile-activity-summary.ts |
| Divs share | apps/frontend/src/server/lib/specialty-share.ts |
| Log | apps/frontend/src/components/profile/HistoricalLog.tsx |
| Log filters | apps/frontend/src/components/profile/HistoricalLogMenu.tsx |
| Profile URL | apps/frontend/src/lib/profile-url.ts |
| Metrics and div files | apps/frontend/src/lib/profile-document.ts |
| Div index and month rollup | apps/frontend/src/lib/profile-catalog.ts |
| Profile document load | apps/frontend/src/lib/profile-query.ts |
| Bookmarks | apps/frontend/src/components/bookmarks/BookmarksPage.tsx |
| Hover card | apps/frontend/src/components/ui/UserHoverCard.tsx |