refactor: rename workspace branding and update project overview

This commit is contained in:
2026-09-28 18:54:10 +09:00
parent 1cf32705a7
commit 57882cf5f9
13 changed files with 79 additions and 66 deletions
+59 -46
View File
@@ -1,54 +1,67 @@
# Twitter Lite
# Personal Workspace
Twitter Lite is a personal research deck for Twitter and Mastodon. Create
multiple named decks and arrange up to six columns per deck. Each column is
bound to a connection account, so platforms and multiple accounts work side by side.
A personal workspace for understanding the day, choosing the next action,
keeping context, and exploring information. Home brings together a short brief,
flexible tasks and routines, messages, notes, and reading. A shared sidebar opens
each dedicated workspace; its bottom avatar manages connected accounts.
## Scope
The interface is in English. User-entered text and external posts retain their
original language.
- Multiple deck profiles with creation, selection, renaming, and deletion
- Twitter search, user timeline, and list columns with independent relay accounts
## Workspaces and current status
| Workspace | Route | Current implementation |
| --- | --- | --- |
| Home | `/` | State-derived daily brief, flexible tasks and routines, and natural-language action proposals. Local prototype. |
| Messages | `/support` | Sample conversations, editable replies, scheduling previews, and deferral. Sending is simulated. |
| Journal | `/journal` | Notes, resume points, medication and weight entries, append-only corrections, and summary previews. Local prototype. |
| Reader | `/inbox` | Searchable sample articles and a responsive article reader. Recommendation from activity history is not connected yet. |
| Vitals | `/vitals` | Body and Environment dashboards with sample readings, charts, and simulated controls. No health or device connection. |
| Research | `/deck` | Twitter and Mastodon columns, connected accounts, persistent decks, and optional Codex research chat. |
Home, Messages, and Journal share in-memory state across navigation; reloading
resets it. The home input uses a deterministic English/Japanese interpreter to
preview an action before confirmation, rather than an LLM. Research decks,
connections, and chat history use server-side persistence.
See [execution support](docs/execution-support.md) and the
[vitals preview](docs/vitals-preview.md) for prototype scope.
## Direction: personal context and execution
The intended connection with the separate private `life` / Rensheng repository
is a loop: read relevant personal context, suggest useful next actions, record
what happened, and compile meaningful outcomes back into durable context.
This integration is planned; the app does not currently read or update life.
Rensheng would retain personal context, preferences, and the background of
ongoing matters. This workspace would own its execution state and interaction
history, with original conversations and appointments remaining in their source
systems. Context should carry source references and verification dates.
Interests are queries over activity history, not settings the user must maintain.
Routines provide optional guidance rather than a daily completion quota. Home
should help the user choose a manageable next step, with room to defer, dismiss,
or change direction.
## Research capabilities
- Multiple named decks with up to six columns each
- Twitter search, user timelines, and lists with independent relay accounts
- Mastodon OAuth, multiple accounts, search, user, list, and hashtag columns
- Native X search syntax, Top/Latest ranking, and optional `filter:follows`
- List discovery for the profile selected in the column editor
- Column editing, ordering, deletion/undo, manual refresh, and cursor pagination
- Platform logos in column headers distinguish Twitter and Mastodon at a glance
- SQLite-backed shared decks, revision conflicts, and device-local active selection
- Temporary views for AI exploration, with explicit save and temporary copies
- List discovery for the account selected in the column editor
- Column editing, ordering, deletion/undo, refresh, and cursor pagination
- SQLite-backed shared decks, revision conflicts, and device-local selection
- Temporary views for exploration, with explicit saving
- Server-only encrypted Mastodon credentials and configurable Tailscale owner access
- Read-only cards with original-post links, text, media, and quotes
- Experimental WebMCP tools to manage decks and read or paginate their columns
- Prototype: chat with a resident home Codex beside a live deck, reuse existing
decks and write cited Markdown research reports on the host
- Saved chat history with deck, account and citation restoration across devices
and backend restarts
- Read-only post cards with source links, text, media, and quotes
- Experimental WebMCP tools to manage decks and read their columns
- Optional resident Codex research chat, cited host-side Markdown reports,
and saved conversations with deck, account, and citation restoration
The system interface is in English; user-entered text and external posts retain their original language.
`/` opens the daily home with a greeting, natural-language action proposals,
flexible tasks/routines, messages, notes, and reading. `/deck` opens Twitter Lite.
The shared sidebar also opens
the article reader at `/inbox`. This UI-only prototype shows articles inferred
from past reading, searches, and research, with no manual interest settings.
Articles are fictional samples; search and article selection work locally.
On mobile, the article list and reader switch views. The shared sidebar’s bottom
avatar opens connected-account management from either workspace.
The shared sidebar also contains `/support` (communication support) and `/journal`
(notes and health records). These use fictional conversations and local session
state to preview reply review, scheduling, deferral, recording, and corrections.
See [execution support](docs/execution-support.md) for scope and limitations.
`/vitals` is a separate [health/environment reference preview](docs/vitals-preview.md)
with sample scores, body systems, environmental charts, and simulated controls.
The former SNS reader, search,
list, user-profile, and conversation routes have been removed. Original-post
links on post cards open their source site; SNS reply threads are not rendered
inside the app.
Bluesky/Threads/Nostr connectors are not implemented yet. An external browser
agent can also create and read decks through WebMCP.
Original-post links open their source site; SNS reply threads are not rendered
inside the app. Bluesky, Threads, and Nostr connectors are not implemented.
## Requirements and setup
@@ -129,10 +142,10 @@ accounts' lists before planning new searches, then read relevant list columns
for evidence. Follow-ups can reuse catalogs already fetched in the conversation.
Manual deck creation and column editing remain
available. The backend handles tools even when the browser is closed.
Use **新しいチャット** to leave the current conversation and start a fresh Codex
Use **New chat** to leave the current conversation and start a fresh Codex
thread with your next message. The open deck and host reports remain available.
If a turn is running, stop it first. The conversation reset is shared across
open devices. **チャット履歴** reopens saved conversations with their account
open devices. **Chat history** reopens saved conversations with their account
selection, deck and citations; the next message resumes the same Codex thread.
SQLite stores conversation snapshots and the active conversation selection.
Each turn is limited to 6 columns, 12 upstream requests shared between list
+2 -2
View File
@@ -1,7 +1,7 @@
# Execution support prototype
The shared sidebar opens `/support` (Messages) and `/journal` (Journal), alongside
Reader and Twitter Lite. These are UI-only prototypes with fictional contacts
Reader and Research. These are UI-only prototypes with fictional contacts
and in-memory state. Reloading resets the prototype state. Tasks, communication status, outcomes, and
journal entries are shared between routes for the current browser session.
No messages, calendar events, medical records, or Rensheng pages are written.
@@ -41,7 +41,7 @@ Start here, Routines, and If there’s room. There are no fixed time slots.
Tasks can be reordered, completed, deferred, restored, or followed as routine
steps. Communications can be reviewed directly in a home dialog; changes also
appear in `/support`. Journal entries and corrections appear on the home, and
reader samples open for reading. Twitter Lite remains a separate work surface.
reader samples open for reading. Research remains a separate work surface.
The input currently uses a deterministic English/Japanese mock interpreter, not an LLM.
It proposes typed actions for task creation/status/reordering, routine steps,
+1 -1
View File
@@ -4,7 +4,7 @@
`/deck` renders the research workspace; `/` is the integrated daily home. A collapsible shadcn/ui
sidebar based on `sidebar-09` shares its navigation rail with the article reader
at `/inbox`. Within Twitter Lite, it switches between research chat and deck management.
at `/inbox`. Within Research, it switches between research chat and deck management.
Deck management switches decks and adds columns. The avatar at the bottom of the
shared sidebar opens connected-account management in a dialog from either workspace.
Up to six ordered columns scroll independently; only the active deck
+1 -1
View File
@@ -1,6 +1,6 @@
# Shared storage and Mastodon OAuth
Twitter Lite runs as a single personal server behind Tailscale Serve. The
Personal Workspace runs as a single personal server behind Tailscale Serve. The
backend binds to loopback and accepts only the configured Tailscale login.
Browser requests that change state must have the configured Origin. OAuth
callbacks also pass the owner check; the browser must be able to reach the
+2 -2
View File
@@ -41,7 +41,7 @@ test('selects articles and keeps the reader focused on their content', async ({
).toBeVisible()
})
test('recovers from empty search and shares navigation with Twitter Lite', async ({
test('recovers from empty search and shares navigation with Research', async ({
page,
a11y,
}) => {
@@ -61,7 +61,7 @@ test('recovers from empty search and shares navigation with Twitter Lite', async
await expect(
page.getByRole('button', { name: 'Edit interests', exact: true }),
).toHaveCount(0)
await page.getByRole('link', { name: 'Twitter Lite', exact: true }).click()
await page.getByRole('link', { name: 'Research', exact: true }).click()
await expect(page).toHaveURL(/\/deck$/)
await page.getByRole('link', { name: 'Reader', exact: true }).click()
await expect(page).toHaveURL(/\/inbox$/)
+4 -4
View File
@@ -100,13 +100,13 @@
let cfg = config.services.twitter-lite;
in {
options.services.twitter-lite = {
enable = lib.mkEnableOption "Twitter Lite";
enable = lib.mkEnableOption "Personal Workspace";
package = lib.mkOption {
type = lib.types.package;
default = mkTwitterLite pkgs;
defaultText = lib.literalExpression "twitter-lite.packages.\${pkgs.system}.default";
description = "Twitter Lite package to run.";
description = "Personal Workspace package to run.";
};
relayBaseUrl = lib.mkOption {
@@ -143,13 +143,13 @@
port = lib.mkOption {
type = lib.types.port;
default = 3000;
description = "Port on which Twitter Lite listens.";
description = "Port on which Personal Workspace listens.";
};
};
config = lib.mkIf cfg.enable {
systemd.services.twitter-lite = {
description = "Twitter Lite";
description = "Personal Workspace";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment = {
+1 -1
View File
@@ -119,7 +119,7 @@ function Workspace({
<SidebarInset className="min-w-0 overflow-hidden">
<header className="flex h-12 shrink-0 items-center gap-2 border-b px-3">
<SidebarTrigger aria-label="Toggle sidebar" />
<span className="text-sm font-medium">Twitter Lite</span>
<span className="text-sm font-medium">Research</span>
<span className="ml-auto text-xs text-muted-foreground">
Research workspace
</span>
+1 -1
View File
@@ -64,7 +64,7 @@ export function WorkspaceNavigation({
active: pathname === '/inbox',
},
{
title: 'Twitter Lite',
title: 'Research',
to: '/deck',
icon: Search,
active: pathname === '/deck',
+1 -1
View File
@@ -548,7 +548,7 @@ export function HomePage() {
))}
</section>
<Link to="/deck" className="home-deck-link">
Explore in Twitter Lite
Explore in Research
<ArrowUpRight className="size-4" />
</Link>
</aside>
+1 -1
View File
@@ -148,7 +148,7 @@ describe('home natural-language proposals', () => {
['連絡を開いて', '/support'],
['記録', '/journal'],
['リーダーを開く', '/inbox'],
['Twitter Liteに移動', '/deck'],
['Researchに移動', '/deck'],
])('recognizes navigation: %s', (input, to) => {
expect(interpretAction(input, tasks, contacts)).toMatchObject({
action: { type: 'navigate', to },
+4 -4
View File
@@ -38,13 +38,13 @@ export function interpretAction(
return { error: 'Please review this action in the relevant screen.' }
text = text
.replace(
/^open (messages|journal|reader|twitter lite|deck)$/i,
/^open (messages|journal|reader|research|deck)$/i,
(_, name: string) =>
({
messages: '連絡',
journal: '記録',
reader: 'リーダー',
'twitter lite': 'Twitter Lite',
research: 'Research',
deck: 'デッキ',
})[name.toLowerCase()] ?? name,
)
@@ -83,7 +83,7 @@ export function interpretAction(
連絡: '/support',
記録: '/journal',
リーダー: '/inbox',
'Twitter Lite': '/deck',
Research: '/deck',
デッキ: '/deck',
}
const destination = text.replace(/(?:を開いて|を開く|に移動)$/, '')
@@ -93,7 +93,7 @@ export function interpretAction(
if (route)
return {
action: { type: 'navigate', to: route },
label: `Open ${{ '/support': 'Messages', '/journal': 'Journal', '/inbox': 'Reader', '/deck': 'Twitter Lite' }[route]}`,
label: `Open ${{ '/support': 'Messages', '/journal': 'Journal', '/inbox': 'Reader', '/deck': 'Research' }[route]}`,
}
const note = text.match(/^(メモ|再開地点)[::\s]+(.+)$/s)
+1 -1
View File
@@ -87,7 +87,7 @@ async function getApp(origin: string, database: AppDatabase, send: Send) {
const registered = appSchema.parse(
await (
await formRequest(send, origin, '/api/v1/apps', {
client_name: 'Twitter Lite',
client_name: 'Personal Workspace',
redirect_uris: redirectUri,
scopes,
})
+1 -1
View File
@@ -19,7 +19,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
content: 'width=device-width, initial-scale=1',
},
{ name: 'referrer', content: 'no-referrer' },
{ title: 'Twitter Lite' },
{ title: 'Personal Workspace' },
],
links: [
{ rel: 'stylesheet', href: appCss },