Skip to content

Cross-project stores

A cross-project store turns felt into a wiki or knowledge base that spans many directories and projects. Each project keeps its own fiber tree where the project lives, and one store gathers those trees into a single body of knowledge you can search and link across. The question "have I solved this before?" gets an answer without first remembering which repo it was in. Threads that belong to no single project — recurring conversations, admin, notes about the tools themselves — get a home that stays out of every project's tree. And reading across the whole body now and then turns up a pattern from one project that answers a question in another, which no per-project view could show you.

A felt store is a plain .felt/ directory, and a filesystem symlink is all it takes to make one project's tree a subtree of a larger store. The same bytes then sit at two paths: <project>/.felt/ and ~/store/.felt/<project>/.

Name your cross-project store whatever you like

You can call your cross-project store loom, for example — felt neither ships that name nor expects it. Yours can live anywhere and be called anything; the rest of this page uses ~/store as a neutral stand-in.

Views and the store

When a project's .felt/ is a symlink into a larger store, felt run from the project knows it is looking at a view of that store and treats the two differently.

felt ls and felt tree stay inside the view. They list the project's own fibers under short ids relative to the view (covariance, not my-project/covariance), and they stay fast however large the store grows.

felt find searches the whole store. Hits inside the view print first under their local ids; the rest follow under a separator naming the enclosing store, each under its full id there.

felt ls "jackknife"      # this project only
felt find "jackknife"    # this project, then everything else in the store

Those full ids work anywhere felt takes an id. felt show, edit, nest and rm act on the fiber where it lives, so you can read or reshape a fiber from another project without leaving the one you are in:

felt show commons/jackknife
felt edit commons/jackknife -o "Settled: 200 patches."

At the top of the store itself there is no enclosing view, and ls, tree and find all cover every project linked in.

The -C, --directory <dir> flag runs felt as if it had been started in dir, and works with every verb. It is the way to reach a store from somewhere that is not a view of it:

felt -C ~/store tree
felt -C ~/store find "jackknife"

Which end holds the real bytes

One end of the symlink holds the real files and the other points at them. Both directions work, but they are not symmetric.

The store as canonical

~/store/.felt/<project>/     ← real files
<project>/.felt/             ← symlink → ~/store/.felt/<project>/

This is the typical case. One git repository, the store's, backs up every project, and felt run inside a project sees it as a view of the store, with everything in the previous section available.

The project as canonical

<project>/.felt/             ← real files
~/store/.felt/<project>/     ← symlink → <project>/.felt/

Here the project owns the bytes and the store points in. Use it when the project has its own reason to keep the files inside its perimeter.

The store still sees the project as one of its subtrees, so tree, find and links from the store side behave as above. The project side, though, is an ordinary top-level store. Nothing in <project>/.felt/ says a larger store points at it, so felt run there sees only the project: find does not reach the rest of the store and ids from elsewhere in it do not resolve. Use felt -C ~/store for anything cross-project.

The store's own git history records the symlink, not the fibers behind it; the project's fibers are versioned wherever the project versions them.

Choosing

If the project… Direction
is a normal repo and you control all the sync store as canonical
has its own git remote, separate from the store's project as canonical
sits in iCloud / Dropbox / a folder-scoped sync service project as canonical
holds content that must not enter the store's history project as canonical

Linking a project without losing fibers

ln -s over an existing .felt/ either fails (a regular directory is in the way) or silently replaces it (an existing symlink is). Either way fibers can vanish, so move the old side aside first and remove it only once the link is verified.

# 1. Check both ends for existing content.
ls <project>/.felt/
ls ~/store/.felt/<project>/

# 2. If both have content, merge by hand BEFORE linking. Inspect every name
#    collision and decide which fiber is the keeper. Copy the non-overlapping
#    fibers from the side that will become the symlink into the canonical side.

# 3. Move the side that will become the symlink out of the way. Do not delete it.
mv <project>/.felt <project>/.felt.pre-link

# 4. Create the symlink (store as canonical shown here).
ln -s ~/store/.felt/<project> <project>/.felt

# 5. Verify from both sides.
felt -C <project> ls -s all
felt -C ~/store tree

# 6. Only once verified, remove the backup.
rm -rf <project>/.felt.pre-link

For the project as canonical, the move-aside happens at ~/store/.felt/<project> and the symlink points the other way: ln -s <project>/.felt ~/store/.felt/<project>. The discipline is the same.

Never rm -rf either side before verifying. Fibers hold accreted context that is expensive to reconstruct.

A [[wikilink]] resolves against the store felt is reading, so what resolves depends on which store that is.

Inside a view of a store, a link can reach any fiber in the whole store. Local ids ([[covariance]]), full store paths to fibers in this project ([[my-project/covariance]]) and full paths to fibers anywhere else ([[commons/jackknife]]) all resolve, and felt check run from the project treats them as sound.

From a project that is canonical, only the project's own fibers resolve on its side. A link to [[commons/jackknife]] written there is sound when checked from ~/store and flagged as broken when checked from the project, since the project cannot see the store.

Two stores that are not joined by any symlink cannot see each other at all. A link from one into the other reads as broken from either side, and the warning is accurate. If the link matters, join the stores; mirroring the target into both is worth it only for a stable reference document, and often the link reads just as well as prose. Do not silence the warning by inventing a stub fiber.

If the shuttle daemon runs on this machine

The daemon polls the stores it is given and assumes none. Point it at a fresh store on a machine where your fibers already live in other stores, and you get a healthy daemon serving an empty board — no error, because nothing is wrong.

A cross-project store is the tidy answer: felt re-discovers a store's symlinked substores, so naming the store reaches every project linked into it, and a new project joins by symlink rather than by reconfiguring the daemon. See Configuring stores for where the store list is set, and its Sharp edges for the macOS constraint that decides the direction: launchd cannot read ~/Documents, ~/Desktop or ~/Downloads at all, so a project in one of those can only be daemon-visible if its bytes are canonical elsewhere.