Things I didn't ship
A lot of product writing is really post-hoc justification for what shipped. I think the more interesting list is what I didn't ship.
With Portrait, there were a few ideas that were clearly right in principle and still never made it into the product. Not because they were bad, but because time, focus, and operational reality kept winning.
Client-side verification
I didn't add client-side IPLD verification for data returned by an IPFS gateway, or for data returned through Arweave and Irys. The browser mostly trusted the gateway response and moved on.
That was not because the idea was impossible. In the upload path we were already attaching the IPFS CID into the Irys and Arweave tags, which means the client could have checked that the bytes it received actually matched the expected content address instead of trusting the URL.
The annoying part is that we already did a stricter version elsewhere. In portrait-hosting-app, incoming portrait objects are hashed again and compared against the CID stored in PortraitStateRegistry. I never pushed that same verification discipline all the way into the browser for gateway-fetched content.
I still think that is the right direction. If you are going to call something decentralized storage, the client should verify more than just whether a gateway answered.
History as a graph
I didn't build the git-like history view the protocol was pointing toward. The pieces were already there: ownership changes existed onchain, and the publish flow was already carrying previousPortraitHash, blockNumber, and chainId inside the portrait metadata.
That is enough to do something much more interesting than a flat changelog. One layer could crawl owner epochs from PortraitRegistered, PortraitTransferred, and ERC-721 transfer history. Another could follow previousPortraitHash edges across published versions. If two later portraits pointed back to the same previous hash, that is already the beginning of a fork.
I even had Claude Opus 4.5 make a time-machine-like application that let you go back through a Portrait a bit like macOS Time Machine. It was similar to this idea, but not complete: it only checked previousPortraitHash inside the portraitObject and rendered that chain. Still, it was cool. See the prototype on X.
The hosting side made this even more interesting, because it validates the portrait signer against ownership or delegation at that exact block height before recomputing the CID. So the graph would not just be decorative. It could tie together versions, forks, and who was actually allowed to publish each state at that moment in chain history.
That would have made Portrait feel more like a living object with lineage instead of just a current snapshot. We had the raw ingredients for the graph, but not the graph itself.
Bring your own infrastructure
I didn't add settings that let people bring their own RPCs or their own IPFS gateways. In practice, Portrait mostly used Portrait-controlled defaults because that made the product more reliable and much easier to support.
That was the practical choice, but not the most sovereign one. A stronger version of the product would have let advanced users point the app at their own infrastructure, change their media gateway, or decide which RPC path they wanted to trust. The same applies to Arweave and Irys resolution. If the product claims portability, the network path should be portable too.
Those controls matter if you actually care about reducing trust in the app operator. I still think they should exist. I just never shipped them.