@@ -329,105 +329,56 @@ The items below are planned improvements roughly ordered by user value. They are
---
### 1. Username Display Names & Password Changes on Profile
### 1. Auto-Derive Display Casing from Mastodon Posts
**Problem:**Usernames are forced lowercase on registration (some users want "JohnDoe" not "johndoe"). Users also have no self-service way to change their password.
**Problem:**`displayTag` is currently set once at research/creation time from whatever the user typed. In reality Mastodon users have an established "canonical" capitalisation for a tag (e.g. `#StPatricksDay` rather than `#stpatricksday`) and our display tag should reflect that.
**Plan:**
-Add a `displayUsername` field to `User` — stores the original mixed-case version typed at signup. Lookups/URLs continue using the lowercase `username` for uniqueness and consistency.
-Profile settings page (`/profile/[username]/settings` or a tab on the existing profile) with:
- **Change display name** — updates `displayUsername` only; the canonical `username` remains lowercase and immutable (avoids breaking existing links/positions).
- **Change username** — updates both `username` and `displayUsername`; requires confirming no conflicts; all foreign key relations in Prisma use the DB `id` so no cascade issues.
- **Change password** — classic current-password + new-password + confirm form; server endpoint `PATCH /api/user/me` validating current hash before updating.
-All places that show a username (Navbar, profile page, leaderboard, trade history) should render `displayUsername` if set, falling back to `username`.
-During each price update job the worker already fetches up to 200 posts. The Mastodon API returns each post's `tags` array where `name` contains the tag as typed by the poster (e.g. `StPatricksDay`).
-Count the frequency of each distinct casing variant seen across all fetched posts for the hashtag being updated.
- If the most frequent variant differs from the current `displayTag`, update `Hashtag.displayTag` as part of the price update transaction.
- Only update when a variant accounts for a meaningful majority (e.g. ≥ 50% of occurrences) to avoid flip-flopping on low-signal posts.
- Implementation touches: `mastodon.ts` already returns `post.tags` from `getPostsData()` — add a `casing` field to the result (`{ postsPerHour, relatedTags, displayTag? }`); worker checks the returned `displayTag` and includes it in the Prisma update when it differs.
-No schema changes required — `displayTag` already exists on `Hashtag`.
---
### 2. Leaderboard
### 2. Home Page Holdings Summary (Signed-In)
**Problem:**No public way to see who the top players are or browse other users' portfolios.
**Problem:**Signed-in users land on the home page and see only the generic trending list — there is no personalised hook to show how their portfolio is performing.
**Plan:**
-New page `/leaderboard` showing top N players sorted by **net worth** (balance + sum of position value at current prices).
-Table columns: rank, avatar/username link → their public profile, net worth, total trades, biggest single position.
-Public profiles — any user can view `/profile/[username]`; sensitive data (exact balance) optionally hidden from non-owners/non-admins.
-Navbar: show **Leaderboard** link when signed in (replacing or supplementing the current unauthenticated-only links).
-Consider a separate "all-time best trade" leaderboard tab for engagement.
-Add two summary cards above the "Trending now" section, visible only to signed-in users: **Biggest Gain** and **Biggest Loss** (by unrealised P&L across open positions).
-Both cards link to the relevant hashtag page.
-Implemented as a server component addition to `src/app/page.tsx`: query the current user's open positions (joined with current price) and compute unrealised P&L per position to find the top and bottom.
-If the user has no positions, the cards are omitted (no empty-state clutter).
-No schema changes required.
---
### 3. Lucky Dip Lottery
### 3. Search Autocomplete
**Problem:**Players who go broke have no recovery mechanic; there is no daily engagement hook beyond trading.
**Problem:**The current search/research input is a plain text field with no discovery aid — users must know or guess full hashtag names.
**Plan:**
-New page `/lottery`.
-Once per day (tracked by a `lastLotteryAt` timestamp on `User`), a player who has **$0 or less** can reveal one hidden box from a grid (e.g. 5 × 5 = 25 boxes).
-One box is the winner (selected server-side at draw time, stored encrypted or revealed only on pick to prevent client-side cheating).
-**Prizes** (configurable): e.g. one box pays $100, a few pay $10, the rest pay $0.
- Consider allowing players with any balance to play for a small stake (e.g. pay $5 to play, win up to $200) to make it useful beyond the broke scenario.
- Future: admin-configurable prize pool and grid size.
-While the user types in the search box, show a dropdown of matching hashtags that are **already tracked** on the exchange (fetched from a new `GET /api/hashtags/search?q=` endpoint).
-The autocomplete only surfaces existing active hashtags; submitting a tag that doesn't appear in the list still proceeds through the normal research flow (no interference with new-hashtag discovery).
-Client-side: debounce the input (~300 ms), cancel in-flight requests on new keystrokes, close dropdown on `Escape` or outside click.
-Endpoint: case-insensitive prefix match on `Hashtag.tag` where `isActive = true` and `isBanned = false`, returning `{ tag, displayTag, currentPrice }` for up to 8 results.
-No schema changes required.
---
### 4. Home Page Nav Changes (Signed-In vs Signed-Out)
### 4. Open Positions Full Page
**Problem:**Home page shows the same call-to-action links regardless of auth state.
**Problem:**The profile page shows open positions in a compact list — there is no space for richer per-position data (cost basis, current value, a mini P&L chart).
**Plan:**
-When **signed out**: show Sign Up / Sign In CTAs as currently.
-When **signed in**: replace or supplement with quick links to **Leaderboard**, **Lottery**, and the user's own profile.
-Navbar already hides/shows some items; extend that logic to the hero section of the home page.
---
### 5. Related Hashtags
**Problem:** No discovery path from one hashtag to related ones.
**Plan:**
- During price update jobs the worker already fetches up to 200 posts. Parse each post's `tags` array (present in the Mastodon API response) to collect co-occurring hashtags.
- Store co-occurrence counts in a new `RelatedHashtag` table: `(hashtagId, relatedTag, coOccurrences)` — upsert on each price update, incrementing counts.
- On the hashtag detail page, show the top 5 most co-occurring tags as "Related" chips that link to their own pages.
- Only surface related tags that are themselves active (or researchable) — avoid surfacing banned/inactive noise.
- Worker change: `MastodonPost` type already has `content`; add `tags: { name: string }[]` to the interface and parse it in `mastodon.ts`.
---
### 6. Admin: Price Audit Log
**Problem:** Admins have no visibility into the math behind a given price update.
**Plan:**
- The `PriceHistory` table already stores `postsPerHour` alongside `price`. Surface this in two places:
1.**Hashtag detail page** — show the most recent `postsPerHour` reading and the formula result as a tooltip or sub-line under the current price (e.g. _"$1.49 — 5.96 posts/hr at last update"_).
2.**Admin stocks page** — add a "History" drawer/modal per hashtag showing the last N `PriceHistory` rows as a table: `recordedAt | postsPerHour | price | Δ price` so admins can see the full audit trail.
- No schema changes needed — `postsPerHour` is already stored.
---
### 7. Admin: Add Hashtag Manually
**Problem:** Admins cannot bypass the research-point system to seed the exchange with interesting hashtags.
**Plan:**
- "Add hashtag" button on `/admin/stocks` that opens a modal with a tag input.
- API: `POST /api/admin/stocks` — normalizes tag, queries Mastodon immediately (same as research flow), upserts hashtag, queues an initial price-update job. Returns error if Mastodon returns nothing.
- Optionally allow admin to force-add with a manual starting price even if Mastodon has no results (useful for bootstrapping/testing).
---
### 8. Admin: Ban / Block Hashtags
**Problem:** No way to prevent abusive or NSFW hashtags from being researched into the exchange.
**Plan:**
- Add `isBanned: Boolean @default(false)` field to the `Hashtag` model.
- Admin UI: "Ban" button on the stocks page that sets `isBanned = true` and `isActive = false`. Banned hashtags still exist in the DB for record-keeping but are not researchable.
- Research API (`/api/research`): check `isBanned` on any existing matching hashtag — if banned return a `403` with a user-friendly message (e.g. _"This hashtag is not available on HashEx."_) without spending a research point.
- New pre-ban blocklist: a static list (or admin-editable table) of tags that are auto-banned on first research attempt, before any Mastodon query.
- Banned tags should still appear in the admin stocks list with a distinct visual indicator and an "Unban" toggle.
-New page `/positions` (auth-protected) showing only the signed-in user's open positions in a more detailed layout.
-Each row / card: hashtag name + link, position type (LONG/SHORT), shares held, average buy price, current price, total cost basis, current value, unrealised P&L and P&L %, a sparkline chart of the hashtag's recent price history.
-Link into this page from the "Open positions" heading on the profile page.
- No new nav item needed — accessed via profile page link.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.