ThinkWrite is a Laravel-based thinking workspace designed to capture, connect, and evolve ideas through a structured thought model.
Instead of treating ideas as isolated notes, ThinkWrite organizes ideas as connected thought entities with relationships such as links, evolution, and synthesis.
The application is implemented as a modular Laravel monolith with server-rendered UI and graph exploration features.
- Laravel 12
- MySQL
- Blade + Alpine.js
- Tailwind CSS
- Sortable.js
- Laravel Breeze
ThinkWrite uses Laravel Breeze for its account authentication flow.
Guest entry points:
GET /loginGET /register
Email templates:
resources/views/emails/verify.blade.php
ThinkWrite includes a reusable SMTP-backed mail layer for verification emails and future system notifications.
Core pieces:
App\Services\MailServiceis the single application mail entry pointApp\Mail\VerifyEmailrenders the verification mailableresources/views/emails/verify.blade.phpcontains the HTML verification template
Verification flow:
- the registration flow creates the user
- ThinkWrite builds a signed verification URL for
verification.verify - the URL host is normalized against
BASE_URLwhen present - the app sends
VerifyEmailthroughMailService - the resend verification route uses the same mail path
SMTP configuration comes from the standard Laravel mail environment variables:
MAIL_MAILERMAIL_HOSTMAIL_PORTMAIL_USERNAMEMAIL_PASSWORDMAIL_ENCRYPTIONMAIL_FROM_ADDRESSMAIL_FROM_NAME
This keeps email delivery infrastructure separate from the thinking engine while preserving a consistent service layer for future notification types.
- Quick thought capture into the active space
- Thought evolution chains with parent-child relationships
- Knowledge graph linking with backlinks and graph exploration
- Thought synthesis for combining multiple ideas into a new direction
- Graph index engine for scalable neighbor traversal
- Idea emergence engine for statistical pattern discovery without AI
- Idea lifecycle system from thought to project execution
- Spatial thinking canvas for freely arranging thoughts on a workspace
- Guided thinking prompts and starter templates to avoid blank-state paralysis
- Daily thinking streaks based on thinking sessions
- Thread view for exploring how an idea changed over time
- Daily review suggestions to prevent idea decay
- Rediscovery sidebar with time-based resurfacing
- Instant thought search with MySQL fulltext support and SQLite fallback
- Drag and drop thought movement between streams
- Immutable thought history with versions, event logs, and exports
The biggest UX risk in a personal thinking tool is opening a blank board and having no obvious next move.
ThinkWrite addresses that with a guided start layer at the top of every board:
- a daily prompt to trigger the first thought
- a smart suggestion based on recent topics
- optional starter templates such as
Idea,Observation,Problem,Question, andInsight - the quick thought input directly below the prompt so capture still happens in one step
Core pieces:
ThinkingPromptService::getDailyPrompt()ThinkingPromptService::getRandomPrompt()ThinkingPromptService::getCategoryPrompt()ThinkingPromptService::getSmartSuggestionForUser()
Prompts are cached through the Redis store when available, with a safe cache fallback for environments where Redis is not running.
UI:
resources/views/components/thinking-prompt.blade.phpresources/views/components/thought-template-selector.blade.php
This keeps the board functional even for first-time or low-momentum sessions.
ThinkWrite tracks momentum through thinking_sessions.
Each time a real thought is captured or evolved, the current day's thinking session increments thought_count.
The streak card reports how many consecutive days the user has captured at least one thought.
Core pieces:
ThinkingSessionService::recordThought()ThinkingSessionService::getStreak()
UI:
resources/views/components/thinking-streak.blade.php
ThinkWrite now supports bidirectional thought linking.
Inline syntax:
[[Distributed systems concept]][[Implementation idea]]
When a linked thought already exists in the same space, the parser creates an edge to it. When it does not exist, ThinkWrite creates a placeholder thought and links to that node.
Implemented pieces:
thought_linksThoughtLinkService::parseLinks()ThoughtLinkService::createLinks()ThoughtLinkService::updateLinks()ThoughtGraphService::getSpaceGraph()ThoughtGraphService::getConnectedThoughts()GET /thoughts/{thought}/linksGET /graph
UI:
- thought cards show
Linked Thoughts - thought cards show
Referenced By resources/views/graph/index.blade.phprenders the graph explorer
This turns the board into a navigable thinking network rather than a flat note list.
ThinkWrite includes a Cytoscape.js powered Graph Explorer at:
GET /graph
API endpoints:
GET /api/thoughts/graphGET /api/thoughts/{thought}/neighbors
Behavior:
- Laravel provides graph node and edge data
- Cytoscape.js renders the interactive graph in the browser
- the initial graph is capped at 100 thought nodes
- hovering a node lazy-loads local neighbors
- clicking a node opens that thought in its board context
Relationship colors:
linkevolutionsynthesis
Screenshot instructions:
- open
/graph - zoom and pan until the graph is framed the way you want
- capture the browser window with your normal screenshot tool
ThinkWrite also supports focused graph exploration at:
GET /graph/{thought}GET /api/thoughts/{thought}/focus
Focus mode behavior:
- one thought becomes the center node
1-hoploads direct neighbors2-hopexpands the graph to neighbors of neighbors- backlinks can be toggled on or off
- synthesis edges can be toggled on or off
- clicking a node recenters the graph on that thought
- the center node is highlighted in orange while neighbor nodes stay teal
This creates a Roam or Obsidian style exploration flow around a single idea without loading the full graph.
ThinkWrite can find the shortest indexed connection between two thoughts.
Routes:
GET /graph/pathGET /api/thoughts/path?from={id}&to={id}
Behavior:
- uses breadth first search over indexed graph neighbors
- supports link, evolution, and synthesis relationships
- limits traversal depth to 6 hops
- limits visited nodes to 500 for safety
- returns the shortest discovered path as a sequence of thought IDs
- highlights and animates the path inside Cytoscape
This helps users navigate the knowledge graph when two ideas are related indirectly rather than through a direct edge.
ThinkWrite now includes a spatial workspace at:
GET /canvas
The canvas lets users:
- position thoughts freely with persisted
xandycoordinates - drag one thought or a selected cluster of thoughts
- pan and zoom around the active space
- view link, evolution, and synthesis edges on the same surface
- lazy load viewport data through
GET /spaces/{space}/canvas - persist coordinates through
POST /thoughts/{thought}/position
Storage:
thought_positions
Fields:
thought_idspace_idxy
Core pieces:
CanvasService::getCanvas()ThoughtPositionService::store()
UI:
resources/views/canvas/index.blade.phpresources/js/canvas.js
Clusters are currently lightweight spatial groupings built from selected thoughts and stream-based suggestions so users can move related ideas together without changing the underlying thought graph.
ThinkWrite can combine multiple thoughts into a new synthesized thought.
Flow:
- select two or more thought cards
- open the synthesis editor
- write the combined thought
- save the synthesis
The synthesized thought keeps explicit source references through:
thought_synthesesthought_synthesis_items
The synthesized output displays Synthesized From on its card, and synthesis edges are included in the knowledge graph so source thoughts point into the new combined idea.
Core pieces:
ThoughtSynthesisService::createSynthesis()ThoughtSuggestionService::getSynthesisSuggestions()POST /spaces/{space}/syntheses
UI:
resources/views/components/thought-multiselect.blade.phpresources/views/components/synthesis-panel.blade.phpresources/views/components/synthesis-editor.blade.php
Synthesis complements the rest of the system:
- evolution deepens a single thought over time
- linking connects related thoughts
- synthesis merges multiple thoughts into a new idea
As the knowledge graph grows, recursive traversal becomes the main performance risk.
ThinkWrite now stores precomputed adjacency rows in thought_graph_index so graph exploration can use indexed lookups instead of recursive joins at request time.
Stored fields:
thought_idlinked_thought_idlink_typedepth
Link types:
directevolutionsynthesis
Core pieces:
ThoughtGraphIndexService::updateGraphIndex()ThoughtGraphIndexService::rebuildGraphIndex()ThoughtGraphIndexService::getConnectedThoughts()ThoughtGraphTraversalService::getConnectedThoughts()GET /thoughts/{thought}/graph
The index updates whenever:
- inline links change
- a thought evolves
- a synthesis is created
Neighbor traversals are cached with keys in this form:
thought_graph:{thought_id}:{depth}
Redis is used when available, with a safe fallback cache path for environments where Redis is not running.
Cache TTL:
- 1 hour for Redis-backed neighbor sets
Cache invalidation runs whenever an indexed thought is rebuilt.
Scalability protections now include:
- indexed adjacency rows instead of recursive traversal
- depth-limited traversal responses
- cached neighbor lookups
RebuildGraphIndexJobfor consistency rebuilds- daily scheduling in
routes/console.php
ThinkWrite can surface hidden connections without AI by indexing tags and scoring repeated structural patterns.
The emergence system looks at:
- shared tags
- shared links
- time proximity
- co-occurrence inside syntheses
New storage:
thought_tag_indexthought_cooccurrence
Core pieces:
ThoughtEmergenceService::calculateTagClusters()ThoughtEmergenceService::calculateCooccurrence()ThoughtEmergenceService::suggestConnections()GET /thoughts/{thought}/suggestionsGET /emergence
Scoring logic:
- 3 or more shared tags increases score significantly
- appearing together inside syntheses increases score
- direct graph links increase score
- close creation times add a smaller boost
UI:
resources/views/components/emerging-ideas.blade.phpresources/views/emergence/index.blade.php
This helps users discover new insights from the graph structure itself instead of relying on external models or APIs.
ThinkWrite now supports the full execution path of an idea.
Lifecycle stages:
thoughtconceptprojecttaskoutcome
Thoughts can be promoted through these stages and turned into real execution objects.
New storage:
thoughts.stageprojectstasks
Core pieces:
IdeaLifecycleService::promoteThoughtToConcept()IdeaLifecycleService::createProjectFromThought()IdeaLifecycleService::createTasksFromProject()IdeaLifecycleService::completeTask()
UI:
- lifecycle controls on thought cards
/projectskanban board
Graph integration:
- thought -> project
- project -> task
This lets the system move from idea development into execution while preserving the original thinking context.
Thoughts are no longer isolated notes.
Each thought can evolve into a new thought through parent_id:
idea -> refined idea -> implementation idea
Implemented pieces:
ThoughtEvolutionService::createEvolution()ThoughtEvolutionService::getThoughtThread()GET /thoughts/{thought}/threadPOST /thoughts/{thought}/evolve
UI:
resources/views/components/evolve-thought-modal.blade.phpresources/views/components/thought-thread.blade.php
Glitter includes a review loop to avoid the “idea graveyard” problem.
thought_reviews stores:
thought_idreviewed_atreview_score
Daily review suggestions are selected by:
- high priority first
- least recently reviewed thoughts first
- randomization within the candidate set
Implemented pieces:
ThoughtReviewService::getDailyReviewSet()POST /thoughts/{thought}/reviewsGET /spaces/{space}/reviews
UI:
resources/views/components/review-panel.blade.php
Search stays on the existing route:
GET /spaces/{space}/search
Search implementation:
- MySQL uses
MATCH(content) AGAINST(...)fulltext search - SQLite test runs fall back to
LIKE
Controllers stay thin and delegate search shaping to services and repositories.
ThinkWrite now preserves thought history instead of treating edits as destructive overwrites.
Storage:
thought_versionsthought_events
Edit behavior:
- creating a thought records version
1 - editing a thought appends a new version row
- the current thought row still acts as the live projection for fast board rendering and search
- delete actions archive thoughts through soft deletion instead of permanently removing them
Version services:
ThoughtVersionService::createInitialVersion()ThoughtVersionService::createVersion()ThoughtVersionService::getVersionHistory()
Event services:
ThoughtEventService::recordEvent()ThoughtEventService::getThoughtEvents()
Recorded event types include:
ThoughtCreatedThoughtEditedThoughtLinkedThoughtSynthesizedThoughtPromotedThoughtReviewedThoughtArchived
UI:
- board cards expose a
Version historypanel - recent event types are shown directly on the thought card
This preserves the chain of thinking rather than hiding how an idea changed.
ThinkWrite can export thought history at:
GET /export/thoughts
Formats:
?format=json?format=markdown
Exports include current thought data, version history, event history, space/stream context, and archive status.
ThinkWrite uses Git tags as the primary application version source.
Examples:
v0.1.0v0.2.0v0.3.0v1.0.0
Version rules:
- semantic versioning is the default strategy
php artisan app:versionreports the current application version- the UI footer and about page display the active version
APP_VERSIONcan override Git detection when needed- if no tag exists, the application falls back to the current short commit hash
ThinkWrite stores generated favicon assets in:
public/favicon/
Included files:
favicon.icofavicon-32x32.pngfavicon-16x16.pngapple-touch-icon.pngsite.webmanifest
These files are integrated through the main application layout without changing any of the existing logo or icon assets used elsewhere in the project.
ThinkWrite now has a platform layer that sits above the domain services.
Top-level flow:
Controller -> Core -> Modules
The kernel lives in app/Core and keeps only essential orchestration concerns:
app/Core/ThoughtThoughtKernel.php
app/Core/GraphGraphKernel.php
app/Core/SearchSearchKernel.php
app/Core/IndexIndexKernel.php
app/Core/ModuleManager.phpapp/Core/Contracts/ThinkingModuleInterface.php
Kernel responsibilities:
- expose stable entrypoints for thought, graph, search, and index operations
- dispatch domain events after core mutations
- invoke registered modules through a shared module contract
- keep controllers from depending on many feature-specific services directly
This keeps ThinkWrite extensible without turning controllers into feature coordinators.
Feature extensions now live in app/Modules:
PromptModuleReviewModuleEvolutionModuleSynthesisModuleEmergenceModuleLifecycleModule
Each module implements ThinkingModuleInterface:
register()boot()processThought()
Module loading is handled by ModuleManager, which:
- discovers module classes from
app/Modules - registers them during application startup
- boots event listeners and module wiring
- allows the kernels to pass changed thoughts through the active module pipeline
This makes features additive. The core stays focused on essential thinking primitives, while modules attach specialized behavior around prompts, reviews, emergence, lifecycle execution, and other higher-level workflows.
The kernel emits events when core thought actions complete:
ThoughtCreatedThoughtLinkedThoughtSynthesizedThoughtReviewed
Modules listen to these events to refresh or react without pushing that logic back into controllers.
Examples:
- emergence listeners can refresh tag and co-occurrence indexes
- review listeners can refresh review state
- synthesis listeners can attach downstream graph or suggestion behavior
This event-driven pipeline allows ThinkWrite to grow as a long-term platform while preserving the existing feature set.
Glitter uses a domain-oriented structure.
Request flow:
Controller -> Core -> Service -> Repository -> Model
app/Domain/SpaceModels/Space.phpRepositories/SpaceRepository.phpServices/SpaceService.php
app/Domain/StreamModels/Stream.phpRepositories/StreamRepository.phpServices/StreamService.php
app/Domain/ThoughtModels/Thought.phpRepositories/ThoughtRepository.phpRepositories/ThoughtEvolutionRepository.phpServices/ThoughtService.phpServices/ThoughtEvolutionService.phpServices/IdeaLifecycleService.phpServices/ThoughtLinkService.phpServices/ThoughtGraphIndexService.phpServices/ThoughtGraphService.phpServices/ThoughtGraphTraversalService.php
app/Domain/ProjectModels/Project.phpRepositories/ProjectRepository.phpServices/ProjectService.php
app/Domain/TaskModels/Task.phpRepositories/TaskRepository.phpServices/TaskService.php
app/Domain/ThoughtLinkModels/ThoughtLink.phpRepositories/ThoughtLinkRepository.php
app/Domain/ThoughtGraphIndexModels/ThoughtGraphIndex.phpRepositories/ThoughtGraphIndexRepository.php
app/Domain/ThoughtEmergenceModels/ThoughtTagIndex.phpModels/ThoughtCooccurrence.phpRepositories/ThoughtTagIndexRepository.phpRepositories/ThoughtCooccurrenceRepository.phpServices/ThoughtEmergenceService.php
app/Domain/ThoughtReviewModels/ThoughtReview.phpRepositories/ThoughtReviewRepository.phpServices/ThoughtReviewService.php
app/Domain/ThoughtSynthesisModels/ThoughtSynthesis.phpModels/ThoughtSynthesisItem.phpRepositories/ThoughtSynthesisRepository.phpServices/ThoughtSynthesisService.phpServices/ThoughtSuggestionService.php
app/Domain/ThinkingPromptModels/ThinkingPrompt.phpRepositories/ThinkingPromptRepository.phpServices/ThinkingPromptService.php
app/Domain/ThinkingSessionModels/ThinkingSession.phpRepositories/ThinkingSessionRepository.phpServices/ThinkingSessionService.php
app/Domain/ThoughtPositionModels/ThoughtPosition.phpRepositories/ThoughtPositionRepository.phpServices/CanvasService.phpServices/ThoughtPositionService.php
app/Domain/ThoughtVersionModels/ThoughtVersion.phpRepositories/ThoughtVersionRepository.phpServices/ThoughtVersionService.php
app/Domain/ThoughtEventModels/ThoughtEvent.phpRepositories/ThoughtEventRepository.phpServices/ThoughtEventService.php
- Controllers handle HTTP concerns, route authorization, and response formatting.
- Core kernels coordinate essential thinking workflows and module execution.
- Services own workflows and transactions.
- Repositories own persistence and query strategy.
- Models define relationships, casts, and domain persistence shape.
Glitter is indexed for large thought collections.
Indexes in use:
spaces.user_id- covered by the foreign key index
streams.space_id- covered by the foreign key index
streams.position- indexed explicitly
thoughts.stream_id- covered by the foreign key index
thoughts.user_id- covered by the foreign key index
thoughts.parent_id- indexed explicitly
thoughts.position- indexed explicitly and also used in composite ordering indexes
thoughts.content- MySQL fulltext index
thought_reviews.thought_id- covered by the foreign key index
thought_reviews.reviewed_at- indexed explicitly
thought_reviews(thought_id, reviewed_at)- indexed explicitly for review history lookup
thought_links.source_thought_id- indexed explicitly
thought_links.target_thought_id- indexed explicitly
thought_links(source_thought_id, target_thought_id)- unique for duplicate edge prevention
thinking_prompts.category- indexed explicitly for category prompt lookup
thinking_sessions(user_id, started_at)- indexed explicitly for streak and daily session lookup
thought_syntheses.user_id- indexed explicitly
thought_syntheses.synthesized_thought_id- indexed explicitly for source reference lookup
thought_synthesis_items.synthesis_id- indexed explicitly
thought_synthesis_items.thought_id- indexed explicitly
thought_graph_index.thought_id- indexed explicitly for neighbor lookup
thought_graph_index.linked_thought_id- indexed explicitly for reverse lookup and rebuild impact
thought_graph_index.depth- indexed explicitly for depth-limited traversal
thought_tag_index.tag- indexed explicitly for tag clustering
thought_tag_index.thought_id- indexed explicitly for reverse tag lookup
thought_cooccurrence.thought_a_id- indexed explicitly for suggestion lookup
thought_cooccurrence.thought_b_id- indexed explicitly for suggestion lookup
thoughts.stage- indexed explicitly for lifecycle filtering
projects.thought_id- indexed explicitly for lifecycle lookup
tasks.project_id- indexed explicitly for project task lookup
These indexes support:
- latest-thought queries
- space search
- thread retrieval
- review selection
- ordered stream rendering
- graph edge traversal
- backlink lookups
- prompt selection by category
- thinking streak calculations
- synthesis lookup
- synthesis graph traversal
- indexed graph neighbor traversal
- cached graph exploration
- tag clustering
- co-occurrence suggestion generation
- lifecycle stage filtering
- project and task board rendering
resources/views/spaces/show.blade.phpis the board shellresources/views/components/quick-thought.blade.phphandles instant captureresources/views/components/search-box.blade.phphandles live searchresources/views/components/review-panel.blade.phphandles daily reviewresources/views/components/rediscover-panel.blade.phphandles resurfacingresources/views/components/evolve-thought-modal.blade.phphandles evolution creationresources/views/components/thought-thread.blade.phphandles thread displayresources/views/graph/index.blade.phphandles graph explorationresources/views/canvas/index.blade.phphandles spatial canvas explorationresources/views/components/thinking-prompt.blade.phphandles guided startsresources/views/components/thought-template-selector.blade.phphandles starter templatesresources/views/components/thinking-streak.blade.phphandles momentum trackingresources/views/components/thought-multiselect.blade.phphandles source selectionresources/views/components/synthesis-panel.blade.phphandles synthesis suggestionsresources/views/components/synthesis-editor.blade.phphandles synthesis creationresources/views/components/emerging-ideas.blade.phphandles statistical idea suggestionsresources/views/emergence/index.blade.phphandles emergence dashboardsresources/views/projects/index.blade.phphandles execution projects and tasksresources/js/board.jscontains board interactionsresources/js/graph.jscontains graph rendering and explorationresources/js/canvas.jscontains spatial canvas interactions
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate
npm install
npm run buildphp artisan serveIn another shell:
npm run devFeature coverage includes:
- Space CRUD
- Standard thought creation
- Quick thought creation
- Thought movement
- Thought evolution
- Thought linking and placeholder creation
- Backlinks and graph API
- Prompt generation and smart prompt selection
- Thinking session tracking and streak calculation
- Thought synthesis and graph integration
- Canvas loading and thought position persistence
- Graph index rebuild and cached traversal
- Idea emergence, co-occurrence scoring, and suggestion endpoints
- Idea lifecycle promotion, project creation, and task completion
- Thought version history and immutable event logging
- Thought review suggestions and recording
- Thought search
- Rediscover endpoint
- Authorization boundaries
Run:
php artisan testThinkWrite uses an Open Core licensing model. The core thinking engine contained in this repository is licensed under:
GNU Affero General Public License v3.0 (AGPL-3.0)
This means:
- you may use, study, and modify the software
- you may run your own instance
- if you modify the software and run it as a network service, you must publish the modified source code See the LICENSE file for details.
ThinkWrite follows an Open Core architecture.
Core Thinking Engine → AGPL-3.0
Plugins / Extensions → may use permissive licenses
Hosted SaaS Platform → proprietary components
This repository contains the core thinking engine and all essential logic required to run a self-hosted instance. The official hosted service may include additional infrastructure components that are not part of the open source distribution.
The names ThinkWrite and Glitter may be used to refer to this project, but they may not be used to promote derivative hosted services without permission.
If you build a public service based on ThinkWrite, please use a different name unless explicitly authorized.
Contributions are welcome.
By submitting a pull request you agree that your contributions may be distributed under the same license as the project.