chore: speed and performance optimizations d5a82a33
Steve · 2025-08-31 21:52 155 file(s) · +496 −706
astro.config.mjs +30 −32
1 -
import { defineConfig, passthroughImageService } from "astro/config";
1 +
import { defineConfig } from "astro/config";
2 2
import mdx from "@astrojs/mdx";
3 3
import tailwind from "@astrojs/tailwind";
4 4
import sitemap from "@astrojs/sitemap";
6 6
7 7
// https://astro.build/config
8 8
export default defineConfig({
9 -
  site: "https://stevedylan.dev",
10 -
  outDir: "dist",
11 -
  image: {
12 -
    service: passthroughImageService(),
13 -
  },
14 -
  markdown: {
15 -
    shikiConfig: {
16 -
      theme: "vesper",
17 -
      wrap: false,
18 -
    },
19 -
  },
20 -
  prefetch: true,
21 -
  integrations: [
22 -
    mdx({}),
23 -
    tailwind({
24 -
      config: {
25 -
        applyBaseStyles: false,
26 -
      },
27 -
    }),
28 -
    sitemap(),
29 -
    react(),
30 -
  ],
31 -
  vite: {
32 -
    ssr: {
33 -
      external: ["node:async_hooks"],
34 -
    },
35 -
    define: {
36 -
      "process.env": process.env,
37 -
    },
38 -
  },
39 -
  output: "static",
9 +
	site: "https://stevedylan.dev",
10 +
	outDir: "dist",
11 +
	compressHTML: true,
12 +
	markdown: {
13 +
		shikiConfig: {
14 +
			theme: "vesper",
15 +
			wrap: false,
16 +
		},
17 +
	},
18 +
	prefetch: true,
19 +
	integrations: [
20 +
		mdx({}),
21 +
		tailwind({
22 +
			config: {
23 +
				applyBaseStyles: false,
24 +
			},
25 +
		}),
26 +
		sitemap(),
27 +
		react(),
28 +
	],
29 +
	vite: {
30 +
		ssr: {
31 +
			external: ["node:async_hooks"],
32 +
		},
33 +
		define: {
34 +
			"process.env": process.env,
35 +
		},
36 +
	},
37 +
	output: "static",
40 38
});
public/blog-images/cloudinary/D59779D7-75D0-4EE8-8C39-6C3EDA5D5CFC_1_105_c_hbikqe.jpg (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/Screenshot-Alacritty-01-05-2024-22-16_2x_hdiy9a.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/Screenshot-Arc-08-01-2024-22-17_2x_ad4w7s.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/Screenshot-Code-07-06-2023-20-19_2x_gz0arj.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/Screenshot-Code-07-06-2023-20-25_2x_kl790i.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/arc-browser-blog-post/c3rjxfzg9fd2f5wjkdje.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/arc-browser-blog-post/jwihj6ivleo9hx90ikag.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/arc-browser-blog-post/m0kdu35asmm6p43t7ml8.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/arc-browser-blog-post/opluqtxq1ceoigepyjwf.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/arc-browser-blog-post/ow0z8neu9fnv3j21uor0.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/arc-browser-blog-post/pkslbdu6vwxmhcsfpiah.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/arc-browser-blog-post/riqyizem3k19rq7gjcot.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/arc-browser-blog-post/s3udhcext52umxrydoum.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/arc-browser-blog-post/skdp4gdysro6bmmipgkb.png (added) +0 −0

Binary file — no preview.

public/blog-images/cloudinary/digital-ocean-1_d3skf1.png (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/42.png (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/QmNkysZ5Roy723sphUST8abmHakKR86K2YHXeeVTU22ASH.png (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/QmU4XNzvRej9soBFdShhSb3KiTpN45hziDCbzdc5hBW1Nk.webp (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/QmVtqSNc6Ff9Zy8vBxr92AcVdh9eAGYkU8zWGLoyTnoeDL.png (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/Qma8MhvupyHTDmBGiH3yz37acPdqu966YF7rrSk9QDXx6B.png (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/ai-mentor.png (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/bafkreifip5jfzeaha4xpankxyhllsixijxz3vvvxjxaahxeuz4lrrpyqpq.jpg (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/bafkreigz4xll63ejcmn7746hyhruik6uxneglaohbxzpcqorhmm5tvn6qu.jpg (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/bafybeicrnbi7yt5wcnaiielxhvb5fb7mocl7k2ub43e3nqwlnfev4zekkm.webp (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/bafybeihderpsuxl43msvzletfuhuqw75ygo3jhbh2psiboate4xc7gzhde.webp (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/image.png (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/leaving-neovim-for-zed.png (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/natspec-contract.png (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/natspec-markdown-ui-2.png (added) +0 −0

Binary file — no preview.

public/blog-images/files-stevedylan-dev/walletfetch.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-0z9cGRbGbAWoflqk7rU0yw.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-29kNYSjhvijup06Mi6OeOA-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-2Ym-4FvWAD65UiaTVZ67uA-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-4xA96GrA9iLYMp5vorcjyQ.jpeg (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-5vzpNGiQSZqe1EX6ghDINg.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-68MMViE3btD8hHuxaTbEzg-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-6DZYT_oYQ-3ejp4rmRa_ZA-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-DIwVzCaXQliVmZNmaBmlDQ-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-IPAefM4T_NiRygdri_DzhA.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-JbX3kWI20G3EaKJNgXfQiQ.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-KxoVDEZFH3mJrlfeguMYjg.jpeg (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-Olex5OAd3sMaugwF_iWGig.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-TF200pv3qCMx41dHZoE_kg-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-Tp42Ey9Uvdb6njsaXHBOTA.jpeg (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-UrdopTgDhErJObLI3V8DwQ.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-Ve3BV5meR4oHFcPhKmaBuw-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-al2t7LEUktAmcd2qKSDikA-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-d6dOx50styHgfdzVUSkPcQ-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-fOoOKMn_texWENXvLbyUwg-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-kJwlNIUnxBRselpxW8f-ig-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-kZZWMcyP0tjhGDU_pCZj-w-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-oCtNaZK3LOx807tA8IEldg-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-v-x8ltZQ0ep6vlmqoPn2WA.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-vWdVJTZgck3BBA5oqHF0-g-2x.png (added) +0 −0

Binary file — no preview.

public/blog-images/medium/1-xQEP67xsPsQ4vU7wjKLFBw.png (added) +0 −0

Binary file — no preview.

public/blog-images/other/62fd1b0820188c4271c6f5ac_Thumbnail1.png (added) +0 −0

Binary file — no preview.

public/blog-images/other/62fe4cfc48a5e05d952dd2c2_IPFS.png (added) +0 −0

Binary file — no preview.

public/blog-images/other/62fe4d182d482c2b9c29f199_IPFS-1.png (added) +0 −0

Binary file — no preview.

public/blog-images/other/63bd95503c654e14fc1b3b00_Slide-16-9-3.png (added) +0 −0

Binary file — no preview.

public/blog-images/other/6410b46677b05b001afa5ff4_2022-02-10_The-Power-of_blog-img-tiny.png (added) +0 −0

Binary file — no preview.

public/blog-images/other/6410b6848afd85df8fe0a193_2023-01-10_How-to-Create_blog-img-tiny.png (added) +0 −0

Binary file — no preview.

public/blog-images/other/6525558858895876456798a8_20231010_How-To-Run-Your-Own-IPFS-Gateway.jpeg (added) +0 −0

Binary file — no preview.

public/blog-images/other/6545bfa112815d6340466066_20231103_How-to-Encrypt-and-Decrypt-Files-on-IPFS-Using-Lit-Protocol-and-Pinata.jpeg (added) +0 −0

Binary file — no preview.

public/blog-images/other/snippets-og.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/D59779D7-75D0-4EE8-8C39-6C3EDA5D5CFC_1_105_c_hbikqe.jpg (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/Screenshot-Alacritty-01-05-2024-22-16_2x_hdiy9a.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/Screenshot-Arc-08-01-2024-22-17_2x_ad4w7s.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/Screenshot-Code-07-06-2023-20-19_2x_gz0arj.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/Screenshot-Code-07-06-2023-20-25_2x_kl790i.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/arc-browser-blog-post/c3rjxfzg9fd2f5wjkdje.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/arc-browser-blog-post/jwihj6ivleo9hx90ikag.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/arc-browser-blog-post/m0kdu35asmm6p43t7ml8.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/arc-browser-blog-post/opluqtxq1ceoigepyjwf.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/arc-browser-blog-post/ow0z8neu9fnv3j21uor0.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/arc-browser-blog-post/pkslbdu6vwxmhcsfpiah.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/arc-browser-blog-post/riqyizem3k19rq7gjcot.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/arc-browser-blog-post/s3udhcext52umxrydoum.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/arc-browser-blog-post/skdp4gdysro6bmmipgkb.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/cloudinary/digital-ocean-1_d3skf1.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/42.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/QmNkysZ5Roy723sphUST8abmHakKR86K2YHXeeVTU22ASH.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/QmU4XNzvRej9soBFdShhSb3KiTpN45hziDCbzdc5hBW1Nk.webp (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/QmVtqSNc6Ff9Zy8vBxr92AcVdh9eAGYkU8zWGLoyTnoeDL.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/Qma8MhvupyHTDmBGiH3yz37acPdqu966YF7rrSk9QDXx6B.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/ai-mentor.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/bafkreifip5jfzeaha4xpankxyhllsixijxz3vvvxjxaahxeuz4lrrpyqpq.jpg (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/bafkreigz4xll63ejcmn7746hyhruik6uxneglaohbxzpcqorhmm5tvn6qu.jpg (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/bafybeicrnbi7yt5wcnaiielxhvb5fb7mocl7k2ub43e3nqwlnfev4zekkm.webp (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/bafybeihderpsuxl43msvzletfuhuqw75ygo3jhbh2psiboate4xc7gzhde.webp (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/image.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/leaving-neovim-for-zed.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/natspec-contract.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/natspec-markdown-ui-2.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/files-stevedylan-dev/walletfetch.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-0z9cGRbGbAWoflqk7rU0yw.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-29kNYSjhvijup06Mi6OeOA-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-2Ym-4FvWAD65UiaTVZ67uA-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-4xA96GrA9iLYMp5vorcjyQ.jpeg (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-5vzpNGiQSZqe1EX6ghDINg.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-68MMViE3btD8hHuxaTbEzg-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-6DZYT_oYQ-3ejp4rmRa_ZA-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-DIwVzCaXQliVmZNmaBmlDQ-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-IPAefM4T_NiRygdri_DzhA.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-JbX3kWI20G3EaKJNgXfQiQ.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-KxoVDEZFH3mJrlfeguMYjg.jpeg (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-Olex5OAd3sMaugwF_iWGig.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-TF200pv3qCMx41dHZoE_kg-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-Tp42Ey9Uvdb6njsaXHBOTA.jpeg (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-UrdopTgDhErJObLI3V8DwQ.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-Ve3BV5meR4oHFcPhKmaBuw-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-al2t7LEUktAmcd2qKSDikA-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-d6dOx50styHgfdzVUSkPcQ-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-fOoOKMn_texWENXvLbyUwg-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-kJwlNIUnxBRselpxW8f-ig-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-kZZWMcyP0tjhGDU_pCZj-w-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-oCtNaZK3LOx807tA8IEldg-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-v-x8ltZQ0ep6vlmqoPn2WA.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-vWdVJTZgck3BBA5oqHF0-g-2x.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/medium/1-xQEP67xsPsQ4vU7wjKLFBw.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/other/62fd1b0820188c4271c6f5ac_Thumbnail1.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/other/62fe4cfc48a5e05d952dd2c2_IPFS.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/other/62fe4d182d482c2b9c29f199_IPFS-1.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/other/63bd95503c654e14fc1b3b00_Slide-16-9-3.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/other/6410b46677b05b001afa5ff4_2022-02-10_The-Power-of_blog-img-tiny.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/other/6410b6848afd85df8fe0a193_2023-01-10_How-to-Create_blog-img-tiny.png (added) +0 −0

Binary file — no preview.

src/assets/blog-images/other/6525558858895876456798a8_20231010_How-To-Run-Your-Own-IPFS-Gateway.jpeg (added) +0 −0

Binary file — no preview.

src/assets/blog-images/other/6545bfa112815d6340466066_20231103_How-to-Encrypt-and-Decrypt-Files-on-IPFS-Using-Lit-Protocol-and-Pinata.jpeg (added) +0 −0

Binary file — no preview.

src/assets/blog-images/other/snippets-og.png (added) +0 −0

Binary file — no preview.

src/content/post/3d-nft-resume.mdx +22 −19
3 3
publishDate: "10 Jan 2023"
4 4
description: "Level up your job search with a dynamic app NFT resume that will wow any employer."
5 5
tags: ["web3", "nfts", "tutorials", "web development"]
6 -
ogImage: "https://global-uploads.webflow.com/629e4fe96456f8219203e7f1/6410b6848afd85df8fe0a193_2023-01-10_How-to-Create_blog-img-tiny.png"
6 +
ogImage: "/blog-images/other/6410b6848afd85df8fe0a193_2023-01-10_How-to-Create_blog-img-tiny.png"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 9
import pinnie from "../../assets/pinnie.png";
11 10
import OutLinkButton from "../../components/OutLinkButton.astro";
12 11
13 -
<OutLinkButton link="https://www.pinata.cloud/blog/resume-app-nft" site="Pinata" image={pinnie} />{" "}
12 +
<OutLinkButton
13 +
  link="https://www.pinata.cloud/blog/resume-app-nft"
14 +
  site="Pinata"
15 +
  image={pinnie}
16 +
/>{" "}
14 17
15 18
Despite the current market conditions, web3 jobs are still on the rise. But competition is at an all-time high. Sure you can build a portfolio of projects to help display your skills, but that’s what everyone else is doing too. What can you do to stand out among the crowds of developers out there like yourself? How are you thinking outside the box?
16 19
35 38
As said in Justin’s tutorial, you can use a free Pinata account to do all of this, but to truly harness the full user experience of this app, using a paid account with a Dedicated Gateway will make this app much faster than if you used a public gateway or depended on external sources. Here is an example of the application through Steve’s Dedicated Gateway:
36 39
37 40
<iframe
38 -
	src="https://files.stevedylan.dev/QmdKYQpczE7giv15Yx2tkk1pkbRe862eaLhTR5e7FjhJ8F.html"
39 -
	frameborder="0"
40 -
	height="500px"
41 -
	width="100%"
42 -
	class="hidden sm:block"
41 +
  src="/blog-images/files-stevedylan-dev/QmdKYQpczE7giv15Yx2tkk1pkbRe862eaLhTR5e7FjhJ8F.html"
42 +
  frameborder="0"
43 +
  height="500px"
44 +
  width="100%"
45 +
  class="hidden sm:block"
43 46
/>
44 -
<Image
45 -
	src="https://global-uploads.webflow.com/629e4fe96456f8219203e7f1/63bd95503c654e14fc1b3b00_Slide%2016_9%20-%203.png"
46 -
	alt="Screenshot of web app"
47 -
	height={1080}
48 -
	width={1920}
49 -
	aspectRatio={1 / 1}
50 -
	class="sm:hidden"
47 +
<img
48 +
  src="/blog-images/other/63bd95503c654e14fc1b3b00_Slide-16-9-3.png"
49 +
  alt="Screenshot of web app"
50 +
  height={1080}
51 +
  width={1920}
52 +
  aspectRatio={1 / 1}
53 +
  class="sm:hidden"
51 54
/>
52 55
53 56
Awesome! Resume app is done, time to turn it into an NFT. Following the guide, Steve created a simple smart contract to deploy the App NFT in a way that he could keep updating it with new versions, very handy if you ever have an update in experience or education. Also keep in mind you can do your own smart contract customization here to do cool stuff, like mass airdrop your NFT to a list of wallets or ENS addresses!
56 59
57 60
```json
58 61
{
59 -
	"name": "Steve's App NFT Resume",
60 -
	"description": "A dynamic NFT resume by Steve",
61 -
	"image": "ipfs://QmTa46bKHxcQCBoNt887X2zNJwAHpAZ93hTXDi9KeJeM4W",
62 -
	"animation_url": "https://stevedsimkins.mypinata.cloud/ipfs/QmdKYQpczE7giv15Yx2tkk1pkbRe862eaLhTR5e7FjhJ8F/index.html"
62 +
  "name": "Steve's App NFT Resume",
63 +
  "description": "A dynamic NFT resume by Steve",
64 +
  "image": "ipfs://QmTa46bKHxcQCBoNt887X2zNJwAHpAZ93hTXDi9KeJeM4W",
65 +
  "animation_url": "https://stevedsimkins.mypinata.cloud/ipfs/QmdKYQpczE7giv15Yx2tkk1pkbRe862eaLhTR5e7FjhJ8F/index.html"
63 66
}
64 67
```
65 68
src/content/post/3d-solana-nfts.mdx +5 −24
3 3
publishDate: "18 May 2022"
4 4
description: "Learn how to scan and mint real life objects on Solana"
5 5
tags: ["web3", "nfts", "tutorials", "web development"]
6 -
ogImage: "https://miro.medium.com/v2/resize:fit:4800/format:webp/1*5vzpNGiQSZqe1EX6ghDINg.png"
6 +
ogImage: "/blog-images/medium/v2/resize:fit:4800/format:webp/1*5vzpNGiQSZqe1EX6ghDINg.png"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 9
import medium from "../../assets/medium.png";
11 10
import OutLinkButton from "../../components/OutLinkButton.astro";
12 11
79 78
80 79
Once viewing it in the web browser, we can download the model by clicking “export.”
81 80
82 -
<Image
83 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*Olex5OAd3sMaugwF_iWGig.png"
84 -
	alt="screenshot of room capture"
85 -
	height={1080}
86 -
	width={1920}
87 -
	aspectRatio={16 / 9}
88 -
/>
81 +
![image](/blog-images/placeholder.png)>
89 82
90 83
From there we want to select the GLTF format and start the download!
91 84
92 -
<Image
93 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*0z9cGRbGbAWoflqk7rU0yw.png"
94 -
	alt="screenshot of aerial export screen"
95 -
	height={1080}
96 -
	width={1920}
97 -
	aspectRatio={16 / 9}
98 -
/>
85 +
![image](/blog-images/placeholder.png)>
99 86
100 87
Ok so we got our 3D file for our environment, now lets do some 3D objects as well! The process is pretty similar, except for 3D objects we’ll switch to the “photo” mode instead of the LiDAR mode. The photo mode will take a bunch of different pictures and put them together to make a 3D model! In the video below you can see what the process looks like as I scan one of my prized possessions, a 1930's edition of Moby Dick illustrated by Rockwell Kent.
101 88
139 126
140 127
Let’s log into Pinata and upload our file. Really simple, just click on the upload button in the top left, give it a name, and upload!
141 128
142 -
<Image
143 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*v-x8ltZQ0ep6vlmqoPn2WA.png"
144 -
	alt="uploading screen at pinata"
145 -
	height={1080}
146 -
	width={1920}
147 -
	aspectRatio={16 / 9}
148 -
/>
129 +
![image](/blog-images/placeholder.png)>
149 130
150 131
Then we just need to choose a subdomain and make sure its available. If it is, click next!
151 132
280 261
281 262
Then using a wallet like Phantom, click on “Add / Connect wallet, select “import private key,” then paste in our keypair from earlier. Once it’s in your wallet you will want to click on the settings icon in the bottom right, scroll down to “change network” and set it to “Devnet.” Once you do that, go back to your NFTs page and you should see it!!
282 263
283 -
![wallet gif](https://miro.medium.com/v2/resize:fit:716/1*V-onKvcCU830IvHxNWHbEg.gif)
264 +
![wallet gif](/blog-images/medium/v2/resize:fit:716/1*V-onKvcCU830IvHxNWHbEg.gif)
284 265
285 266
## Conclusion
286 267
src/content/post/48-hours-disconnected.mdx +2 −3
3 3
publishDate: "02 May 2023"
4 4
description: "I spent two days with zero access to my cell phone or computer, and this is what I discovered"
5 5
tags: ["social media", "tech philosophy", "personal"]
6 -
ogImage: "https://res.cloudinary.com/df9dofjus/image/upload/v1683045399/misc/D59779D7-75D0-4EE8-8C39-6C3EDA5D5CFC_1_105_c_hbikqe.jpg"
6 +
ogImage: "/blog-images/cloudinary/v1683045399/misc/D59779D7-75D0-4EE8-8C39-6C3EDA5D5CFC_1_105_c_hbikqe.jpg"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 9
import substack from "../../assets/substack.png";
11 10
import OutLinkButton from "../../components/OutLinkButton.astro";
12 11
16 15
	image={substack}
17 16
/>{" "}
18 17
19 -
![cover](https://res.cloudinary.com/df9dofjus/image/upload/v1683045399/misc/D59779D7-75D0-4EE8-8C39-6C3EDA5D5CFC_1_105_c_hbikqe.jpg)
18 +
![cover](/blog-images/cloudinary/v1683045399/misc/D59779D7-75D0-4EE8-8C39-6C3EDA5D5CFC_1_105_c_hbikqe.jpg)
20 19
21 20
It was a Thursday afternoon at the chiropractor. I was still on my paternity leave, helping my wife take our kids so our youngest could get some adjustments. Like most dads I sat around, waited, and scrolled on my phone. Halfway through I could tell my wife was miffed, and I thought it was due to our appointment being fifteen minutes late. In reality, my son had been trying to get my attention for several minutes, I ignored him, and he gave up. When she told me later that day I could vividly see it happen, like I was there, but I wasn’t.
22 21
src/content/post/How To Run Your Own IPFS Gateway.mdx +15 −17
1 1
---
2 2
title: "How to Run Your Own Public IPFS Gateway"
3 -
publishDate: "10 Oct 2023"
3 +
publishDate: "2023-10-10T00:00:00.000Z"
4 4
description: "Learn how to run a public IPFS gateway with a custom domain using Digital Ocean"
5 5
tags: ["IPFS", "Gateway", "Dedicated Gateway", "Hosting", "Digital Ocean"]
6 -
ogImage: "https://assets-global.website-files.com/629e4fe96456f8219203e7f1/6525558858895876456798a8_20231010_How%20To%20Run%20Your%20Own%20IPFS%20Gateway.jpeg"
6 +
ogImage: "/blog-images/other/6525558858895876456798a8_20231010_How-To-Run-Your-Own-IPFS-Gateway.jpeg"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 -
11 -
IPFS has proven to be the decentralized storage protocol of choice by many blockchain developers, and one of the crucial tools used to access content on IPFS are [Gateways](https://www.pinata.cloud/blog/what-is-an-ipfs-gateway). IPFS Gateways are like bridges between the IPFS protocol and the HTTP protocol that we use everyday to browse websites. There are lots of different options to choose from when it comes to IPFS Gateways, and in this post we’ll show you how to host and build your own!
9 +
IPFS has proven to be the decentralized storage protocol of choice by many blockchain developers, and one of the crucial tools used to access content on IPFS are [Gateways](https://www.pinata.cloud/blog/what-is-an-ipfs-gateway). IPFS Gateways are like bridges between the IPFS protocol and the HTTP protocol that we use everyday to browse websites. There are lots of different options to choose from when it comes to IPFS Gateways, and in this post we'll show you how to host and build your own!
12 10
13 11
<aside>
14 12
⚠️ Warning! This guide will show you how to make a public IPFS Gateway that can access any CID on IPFS, which means it has the potential to be abused. Please be cautious and look into ways you can secure your gateway.
27 25
28 26
Since we’ll be using DigitalOcean you can head over there to create an account and buy a Droplet. You definitely don’t need anything crazy, I just got the following:
29 27
30 -
<Image
31 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/v1701308413/digital-ocean-1_d3skf1.png"
32 -
	alt="digital ocean droplet creation"
33 -
	width={1920}
34 -
	height={1080}
35 -
	aspectRatio={2 / 1}
28 +
<img
29 +
  src="/blog-images/placeholder.png"
30 +
  alt="digital ocean droplet creation"
31 +
  width={1920}
32 +
  height={1080}
33 +
  aspectRatio={2 / 1}
36 34
/>
37 35
38 36
For the authorization select SSH Keys, then copy and paste the contents of `~/.ssh/rsa.pub` and paste it in as a new key.
39 37
40 -
<Image
41 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/v1701308420/digital-ocean-2_xhdpos.png"
42 -
	alt="digital ocean ssh key creation"
43 -
	width={1920}
44 -
	height={1080}
45 -
	aspectRatio={3 / 1}
38 +
<img
39 +
  src="/blog-images/placeholder.png"
40 +
  alt="digital ocean ssh key creation"
41 +
  width={1920}
42 +
  height={1080}
43 +
  aspectRatio={3 / 1}
46 44
/>
47 45
48 46
After the droplet has been created, you will actually want to turn it off, go to the Network settings, and enable IPV6. Once enabled turn it back on and try to SSH into it with the following command with the IPV4 address of the server:
src/content/post/How to Encrypt and Decrypt Files on IPFS Using Lit.md +104 −104
3 3
publishDate: "04 Nov 2023"
4 4
description: "Experience the power of decentralized storage, encryption, and token gating with this tutorial"
5 5
tags: ["ipfs", "lit-protocol", "encryption", "token-gating"]
6 -
ogImage: "https://assets-global.website-files.com/629e4fe96456f8219203e7f1/6545bfa112815d6340466066_20231103_How%20to%20Encrypt%20and%20Decrypt%20Files%20on%20IPFS%20Using%20Lit%20Protocol%20and%20Pinata.jpeg"
6 +
ogImage: "/blog-images/other/6545bfa112815d6340466066_20231103_How-to-Encrypt-and-Decrypt-Files-on-IPFS-Using-Lit-Protocol-and-Pinata.jpeg"
7 7
---
8 8
9 9
The most popular method used for sharing files off-chain in Web3 is IPFS, and there are some [good reasons for that](https://www.pinata.cloud/blog/why-ipfs-is-the-storage-solution-for-web3-developers). However it does not come without its own share of problems, and one of those is the ability to share private files. IPFS is a public network so anyone with a CID can access and download that content, and this hinders projects that may want to token gate content or create subscriptions to content. With that said, encryption has proven to be one solution to this problem. Remarkably, the solution of [asymmetric encryption](https://www.okta.com/identity-101/asymmetric-encryption) is used in blockchain all the time and can be reused for the purpose of token gating. [Lit Protocol](https://litprotocol.com/) is a decentralized middleware client that enables access controls to help extend asymmetric encryption to token gating based on crypto ownership, such as owning an NFT, ERC-20 token balance, or simply designating a recipient address. In this post, we’ll show you how you can combine the best of both worlds and create an app that will encrypt content, upload it to IPFS, and then given an encrypted CID, decrypt it.
83 83
84 84
```jsx
85 85
const uploadFile = async (fileToUpload) => {
86 -
	try {
87 -
		setUploading(true);
88 -
		const formData = new FormData();
89 -
		formData.append("file", fileToUpload, fileToUpload.name);
90 -
		const res = await fetch("/api/files", {
91 -
			method: "POST",
92 -
			body: formData,
93 -
		});
94 -
		const ipfsHash = await res.text();
95 -
		setCid(ipfsHash);
96 -
		setUploading(false);
97 -
	} catch (e) {
98 -
		console.log(e);
99 -
		setUploading(false);
100 -
		alert("Trouble uploading file");
101 -
	}
86 +
  try {
87 +
    setUploading(true);
88 +
    const formData = new FormData();
89 +
    formData.append("file", fileToUpload, fileToUpload.name);
90 +
    const res = await fetch("/api/files", {
91 +
      method: "POST",
92 +
      body: formData,
93 +
    });
94 +
    const ipfsHash = await res.text();
95 +
    setCid(ipfsHash);
96 +
    setUploading(false);
97 +
  } catch (e) {
98 +
    console.log(e);
99 +
    setUploading(false);
100 +
    alert("Trouble uploading file");
101 +
  }
102 102
};
103 103
```
104 104
204 204
205 205
```jsx
206 206
const uploadFile = async (fileToUpload) => {
207 -
	try {
208 -
		setUploading(true);
209 -
		// Create our litNodeClient
210 -
		const litNodeClient = new LitJsSdk.LitNodeClient({
211 -
			litNetwork: "cayenne",
212 -
		});
213 -
		// Then get the authSig
214 -
		await litNodeClient.connect();
215 -
		const authSig = await LitJsSdk.checkAndSignAuthMessage({
216 -
			chain: "ethereum",
217 -
		});
218 -
		// Define our access controls, this is set to be anyone
219 -
		const accs = [
220 -
			{
221 -
				contractAddress: "",
222 -
				standardContractType: "",
223 -
				chain: "ethereum",
224 -
				method: "eth_getBalance",
225 -
				parameters: [":userAddress", "latest"],
226 -
				returnValueTest: {
227 -
					comparator: ">=",
228 -
					value: "0",
229 -
				},
230 -
			},
231 -
		];
232 -
		// Then we use our access controls and authSig to encrypt the file and zip it up with the metadata
233 -
		const encryptedZip = await LitJsSdk.encryptFileAndZipWithMetadata({
234 -
			accessControlConditions: accs,
235 -
			authSig,
236 -
			chain: "ethereum",
237 -
			file: fileToUpload,
238 -
			litNodeClient: litNodeClient,
239 -
			readme: "Use IPFS CID of this file to decrypt it",
240 -
		});
207 +
  try {
208 +
    setUploading(true);
209 +
    // Create our litNodeClient
210 +
    const litNodeClient = new LitJsSdk.LitNodeClient({
211 +
      litNetwork: "cayenne",
212 +
    });
213 +
    // Then get the authSig
214 +
    await litNodeClient.connect();
215 +
    const authSig = await LitJsSdk.checkAndSignAuthMessage({
216 +
      chain: "ethereum",
217 +
    });
218 +
    // Define our access controls, this is set to be anyone
219 +
    const accs = [
220 +
      {
221 +
        contractAddress: "",
222 +
        standardContractType: "",
223 +
        chain: "ethereum",
224 +
        method: "eth_getBalance",
225 +
        parameters: [":userAddress", "latest"],
226 +
        returnValueTest: {
227 +
          comparator: ">=",
228 +
          value: "0",
229 +
        },
230 +
      },
231 +
    ];
232 +
    // Then we use our access controls and authSig to encrypt the file and zip it up with the metadata
233 +
    const encryptedZip = await LitJsSdk.encryptFileAndZipWithMetadata({
234 +
      accessControlConditions: accs,
235 +
      authSig,
236 +
      chain: "ethereum",
237 +
      file: fileToUpload,
238 +
      litNodeClient: litNodeClient,
239 +
      readme: "Use IPFS CID of this file to decrypt it",
240 +
    });
241 241
242 -
		// Then we turn it into a file that will be accepted by the Pinata API
243 -
		const encryptedBlob = new Blob([encryptedZip], { type: "text/plain" });
244 -
		const encryptedFile = new File([encryptedBlob], fileToUpload.name);
242 +
    // Then we turn it into a file that will be accepted by the Pinata API
243 +
    const encryptedBlob = new Blob([encryptedZip], { type: "text/plain" });
244 +
    const encryptedFile = new File([encryptedBlob], fileToUpload.name);
245 245
246 -
		// Finally we upload the file by passing it to our /api/files endpoint
247 -
		// Keep in mind this works for smaller files and you may need to do a presigned JWT and upload from the client if you're dealing with larger files
248 -
		// Read more about that here: https://www.pinata.cloud/blog/how-to-upload-to-ipfs-from-the-frontend-with-signed-jwts
249 -
		const formData = new FormData();
250 -
		formData.append("file", encryptedFile, encryptedFile.name);
251 -
		const res = await fetch("/api/files", {
252 -
			method: "POST",
253 -
			body: formData,
254 -
		});
255 -
		const ipfsHash = await res.text();
256 -
		setCid(ipfsHash);
257 -
		setUploading(false);
258 -
	} catch (e) {
259 -
		console.log(e);
260 -
		setUploading(false);
261 -
		alert("Trouble uploading file");
262 -
	}
246 +
    // Finally we upload the file by passing it to our /api/files endpoint
247 +
    // Keep in mind this works for smaller files and you may need to do a presigned JWT and upload from the client if you're dealing with larger files
248 +
    // Read more about that here: https://www.pinata.cloud/blog/how-to-upload-to-ipfs-from-the-frontend-with-signed-jwts
249 +
    const formData = new FormData();
250 +
    formData.append("file", encryptedFile, encryptedFile.name);
251 +
    const res = await fetch("/api/files", {
252 +
      method: "POST",
253 +
      body: formData,
254 +
    });
255 +
    const ipfsHash = await res.text();
256 +
    setCid(ipfsHash);
257 +
    setUploading(false);
258 +
  } catch (e) {
259 +
    console.log(e);
260 +
    setUploading(false);
261 +
    alert("Trouble uploading file");
262 +
  }
263 263
};
264 264
```
265 265
394 394
395 395
```jsx
396 396
const accessControlConditions = [
397 -
	{
398 -
		contractAddress: "0xA80617371A5f511Bf4c1dDf822E6040acaa63e71",
399 -
		standardContractType: "ERC721",
400 -
		chain,
401 -
		method: "balanceOf",
402 -
		parameters: [":userAddress"],
403 -
		returnValueTest: {
404 -
			comparator: ">",
405 -
			value: "0",
406 -
		},
407 -
	},
397 +
  {
398 +
    contractAddress: "0xA80617371A5f511Bf4c1dDf822E6040acaa63e71",
399 +
    standardContractType: "ERC721",
400 +
    chain,
401 +
    method: "balanceOf",
402 +
    parameters: [":userAddress"],
403 +
    returnValueTest: {
404 +
      comparator: ">",
405 +
      value: "0",
406 +
    },
407 +
  },
408 408
];
409 409
```
410 410
412 412
413 413
```jsx
414 414
const accessControlConditions = [
415 -
	{
416 -
		contractAddress: "0x50D8EB685a9F262B13F28958aBc9670F06F819d9",
417 -
		standardContractType: "MolochDAOv2.1",
418 -
		chain,
419 -
		method: "members",
420 -
		parameters: [":userAddress"],
421 -
		returnValueTest: {
422 -
			comparator: "=",
423 -
			value: "true",
424 -
		},
425 -
	},
415 +
  {
416 +
    contractAddress: "0x50D8EB685a9F262B13F28958aBc9670F06F819d9",
417 +
    standardContractType: "MolochDAOv2.1",
418 +
    chain,
419 +
    method: "members",
420 +
    parameters: [":userAddress"],
421 +
    returnValueTest: {
422 +
      comparator: "=",
423 +
      value: "true",
424 +
    },
425 +
  },
426 426
];
427 427
```
428 428
430 430
431 431
```jsx
432 432
const accessControlConditions = [
433 -
	{
434 -
		contractAddress: "",
435 -
		standardContractType: "",
436 -
		chain,
437 -
		method: "",
438 -
		parameters: [":userAddress"],
439 -
		returnValueTest: {
440 -
			comparator: "=",
441 -
			value: "0x50e2dac5e78B5905CB09495547452cEE64426db2",
442 -
		},
443 -
	},
433 +
  {
434 +
    contractAddress: "",
435 +
    standardContractType: "",
436 +
    chain,
437 +
    method: "",
438 +
    parameters: [":userAddress"],
439 +
    returnValueTest: {
440 +
      comparator: "=",
441 +
      value: "0x50e2dac5e78B5905CB09495547452cEE64426db2",
442 +
    },
443 +
  },
444 444
];
445 445
```
446 446
src/content/post/a-terminal-based-workflow.mdx +61 −69
5 5
tags: ["programming", "vim", "neovim", "terminal"]
6 6
---
7 7
8 -
import { Image } from "astro:assets";
9 -
10 -
<Image
11 -
	src="https://files.stevedylan.dev/Qma8MhvupyHTDmBGiH3yz37acPdqu966YF7rrSk9QDXx6B.png"
12 -
	alt="header image"
13 -
	height={600}
14 -
	width={800}
15 -
	aspectRatio={3 / 2}
16 -
/>
8 +
![header image](/blog-images/files-stevedylan-dev/Qma8MhvupyHTDmBGiH3yz37acPdqu966YF7rrSk9QDXx6B.png)
17 9
18 10
In a [previous blog post](https://stevedylan.dev/posts/why-i-learned-vim) I talked about why I learned Vim, and how it boosted my speed and productivity. In some ways it’s true, but I don’t think I had a grasp on the whole picture. After I watched [this video](https://youtu.be/5wy2iLU5fs0?si=uZ2e6_EUFkrk4Vrp) I realized it wasn’t necessarily just Vim/Neovim, but my terminal based workflow that was at play. I recently gave [Zed](https://zed.dev) another try as it has some attractive features, including [a whole page in their docs dedicated to Vim.](https://zed.dev/docs/vim) It is minimal with Vim keybindings enabled, as well as pretty speedy since its GPU enable and built on Rust. Nevertheless I still felt clumsy, and I realized why: the terminal. With my current terminal workflow I’m able to easily switch between different projects without having multiple code editor windows opened, and I have better access to CLI tools that compliment my developer workflow. I just can’t replicate that outside of the terminal right now.
19 11
26 18
At the core of my workflow is [Tmux.](https://github.com/tmux/tmux) This tool allows you to create multiple terminal sessions, windows, and panes in just one terminal emulator window. Instead of having a terminal open for every project I might be in, I can just have one. A Tmux session will look like any other terminal window, the difference is the ability to disconnect and then re-attach to that same session. So I can be working on something, leave it, then come right back to it.
27 19
28 20
<video
29 -
	autoPlay
30 -
	muted
31 -
	loop
32 -
	playsinline
33 -
	className="aspect-video w-full"
34 -
	src="https://files.stevedylan.dev/Qmeb4797YyF2FhwTdoPuuQi4LXgdXaGoGcTRqU85JAEv9M.mp4"
21 +
  autoPlay
22 +
  muted
23 +
  loop
24 +
  playsinline
25 +
  className="aspect-video w-full"
26 +
  src="/blog-images/files-stevedylan-dev/Qmeb4797YyF2FhwTdoPuuQi4LXgdXaGoGcTRqU85JAEv9M.mp4"
35 27
></video>
36 28
37 29
Additionally I can create multiple panes and windows inside a session. I generally create a session per project, and each session might have two windows (e.g. one for a client side repo, the other for a server side repo). This both helps keep projects unified yet organized.
38 30
39 31
<video
40 -
	autoPlay
41 -
	muted
42 -
	loop
43 -
	playsinline
44 -
	className="aspect-video w-full"
45 -
	src="https://files.stevedylan.dev/QmWHHCV9YVecdDVcbKXLdfcudW5XBiTt4EfLagP9cowJjC.mp4"
32 +
  autoPlay
33 +
  muted
34 +
  loop
35 +
  playsinline
36 +
  className="aspect-video w-full"
37 +
  src="/blog-images/files-stevedylan-dev/QmWHHCV9YVecdDVcbKXLdfcudW5XBiTt4EfLagP9cowJjC.mp4"
46 38
></video>
47 39
48 40
Where it gets really good is having a solid session manager, and that’s where [Josh Medeski’s Sesh](https://github.com/joshmedeski/sesh) comes into play. With this I can easily change between different sessions, and thus easily switch between different projects. This has become essential to my workflow as I often might be working on 2-3 different projects at a time, all with multiple windows and panes each. Each one can be unique to what I’m working on as well.
49 41
50 42
<video
51 -
	autoPlay
52 -
	muted
53 -
	loop
54 -
	playsinline
55 -
	className="aspect-video w-full"
56 -
	src="https://files.stevedylan.dev/Qmd3SNkNTms4JQCtv4wmFUKS679A2zsrG7e58szLWS3pmM.mp4"
43 +
  autoPlay
44 +
  muted
45 +
  loop
46 +
  playsinline
47 +
  className="aspect-video w-full"
48 +
  src="/blog-images/files-stevedylan-dev/Qmd3SNkNTms4JQCtv4wmFUKS679A2zsrG7e58szLWS3pmM.mp4"
57 49
></video>
58 50
59 51
## Neovim Plugins
63 55
The first is [Telescope](https://github.com/nvim-telescope/telescope.nvim), which is a no brainer for most people already using Neovim. It allows me to quickly jump to different files, search a string through my entire project, or sort through diagnostic issues. It’s incredibly powerful and extensible, and I would highly recommend getting familiar with all its abilities.
64 56
65 57
<video
66 -
	autoPlay
67 -
	muted
68 -
	loop
69 -
	playsinline
70 -
	className="aspect-video w-full"
71 -
	src="https://files.stevedylan.dev/QmTRit4eCktJ87NjxdErZ3csXimNq43G8SueQP77mvtvkE.mp4"
58 +
  autoPlay
59 +
  muted
60 +
  loop
61 +
  playsinline
62 +
  className="aspect-video w-full"
63 +
  src="/blog-images/files-stevedylan-dev/QmTRit4eCktJ87NjxdErZ3csXimNq43G8SueQP77mvtvkE.mp4"
72 64
></video>
73 65
74 66
Another one I use fairly often is [Neo-tree](https://github.com/nvim-neo-tree/neo-tree.nvim). I know some people are anti-file-tree but I personally really enjoy it. It is setup to appear in the middle of my screen, and I use it to help navigate where a file might be, edit a file name, add new files, etc. Since the majority of my work is in JavaScript / Next.js, it’s helpful to distinguish which `page.tsx` or `route.ts` file I’m currently working on.
75 67
76 68
<video
77 -
	autoPlay
78 -
	muted
79 -
	loop
80 -
	playsinline
81 -
	className="aspect-video w-full"
82 -
	src="https://files.stevedylan.dev/QmVSc4amHjygkTcd4Rrx62DaYWMXSYpJD8fBhSinGyChyp.mp4"
69 +
  autoPlay
70 +
  muted
71 +
  loop
72 +
  playsinline
73 +
  className="aspect-video w-full"
74 +
  src="/blog-images/files-stevedylan-dev/QmVSc4amHjygkTcd4Rrx62DaYWMXSYpJD8fBhSinGyChyp.mp4"
83 75
></video>
84 76
85 77
Having an LSP (Language Server Protocol) and completions setup is also essential for a good workflow in Neovim. This is what provides hints, completions, diagnostics, or even docs for the language you’re working in. Cannot state how helpful these are when working in a typed language like Typescript or Go. I would also say it’s beneficial to learn how to set it up manually, and [Typecraft's video](https://youtu.be/S-xzYgTLVJE?si=xG7c-Yx0fkxRHwx0) does a great job showing how it’s done.
86 78
87 79
<video
88 -
	autoPlay
89 -
	muted
90 -
	loop
91 -
	playsinline
92 -
	className="aspect-video w-full"
93 -
	src="https://files.stevedylan.dev/QmdQSzMuPiDEcFhWLMLsabLLsHD4qG74Qh1WtvQ8SnSa1B.mp4"
80 +
  autoPlay
81 +
  muted
82 +
  loop
83 +
  playsinline
84 +
  className="aspect-video w-full"
85 +
  src="/blog-images/files-stevedylan-dev/QmdQSzMuPiDEcFhWLMLsabLLsHD4qG74Qh1WtvQ8SnSa1B.mp4"
94 86
></video>
95 87
96 88
These two are on the smaller side but are still really great to use. The first is [Tmux Navigator.](https://github.com/christoomey/vim-tmux-navigator) This allows you to navigate between an open Neovim pane and a Tmux pane without using a Tmux prefix. For example, instead of navigating with `Ctrl + b - l` I can just use `Ctrl - l`. It’s the same mapping for switching between Neovim panes and Tmux panes, which is a huge quality of life improvement. The other small mention is [blame.nvim.](https://github.com/FabijanZulj/blame.nvim) With this tool I can hit `Space-b` to see line by line who changed what when. This is great when working with other people on a project and you’re trying to find out what changed when. There’s also the LazyGit plugin for Neovim, but it deserves its own section.
100 92
For the longest time I just used git in the command line for handling any of my git needs, but a lot of that changed when I started working on more team oriented projects. Handling git conflicts was a nightmare, as well as going through git history to see what changed at each commit. LazyGit changed all of that. This tool really simplifies things like cherry picking commits, handling conflicts, and viewing rich history. There’s so much it can do that I haven’t even touched the surface on, and I would recommend [this video](https://youtu.be/CPLdltN7wgE?si=7XqMjlwpi5tmWFpA) by its creator to see what’s possible (if you’re like me you’ll also learn more about git itself from it).
101 93
102 94
<video
103 -
	autoPlay
104 -
	muted
105 -
	loop
106 -
	playsinline
107 -
	className="aspect-video w-full"
108 -
	src="https://files.stevedylan.dev/QmZou8CVipfiFxYkYYm3H6BAzKk1ks6mkosTULxk7GgFUb.mp4"
95 +
  autoPlay
96 +
  muted
97 +
  loop
98 +
  playsinline
99 +
  className="aspect-video w-full"
100 +
  src="/blog-images/files-stevedylan-dev/QmZou8CVipfiFxYkYYm3H6BAzKk1ks6mkosTULxk7GgFUb.mp4"
109 101
></video>
110 102
111 103
## Bringing it All Together
113 105
Let’s do a run through of what starting and managing a project might look for me with this workflow. First `t` to start my Tmux session manager, then navigate to my dev folder. There I’ll run a command like `npx create-next-app@latest`. Once the repo is created I’ll `cd` into it and run `nvim` which will greet me with a telescope window of all the files that I can fuzzy find through. Then I might open a split pane to the right with Tmux so I can have a terminal to run commands like `npm run dev`.
114 106
115 107
<video
116 -
	autoPlay
117 -
	muted
118 -
	loop
119 -
	playsinline
120 -
	className="aspect-video w-full"
121 -
	src="https://files.stevedylan.dev/QmUW8QWY1ug3uHzLWtc6F9pFHAurgbjG3qmnc9o6whZDoZ.mp4"
108 +
  autoPlay
109 +
  muted
110 +
  loop
111 +
  playsinline
112 +
  className="aspect-video w-full"
113 +
  src="/blog-images/files-stevedylan-dev/QmUW8QWY1ug3uHzLWtc6F9pFHAurgbjG3qmnc9o6whZDoZ.mp4"
122 114
></video>
123 115
124 116
If the project has multiple repos like a server/client combo or I’m referencing another repo, I’ll create a new window with the same pane setup. As I work and make changes, I’ll open LazyGit in one of my Tmux panes and run `ctrl-b + z` to make it full screen. From there I’ll add my commits and push them up, or make a branch that I can merge main into if I’m already working on a shared project.
125 117
126 118
<video
127 -
	autoPlay
128 -
	muted
129 -
	loop
130 -
	playsinline
131 -
	className="aspect-video w-full"
132 -
	src="https://files.stevedylan.dev/QmdrPRHBkMNCvcbCEaQp9PeUKQsuCHKHgEecBMG6tRnLyB.mp4"
119 +
  autoPlay
120 +
  muted
121 +
  loop
122 +
  playsinline
123 +
  className="aspect-video w-full"
124 +
  src="/blog-images/files-stevedylan-dev/QmdrPRHBkMNCvcbCEaQp9PeUKQsuCHKHgEecBMG6tRnLyB.mp4"
133 125
></video>
134 126
135 127
Back in my main Next.js window I might open another split pane below the right one so while the dev server is running I can make test API calls from the terminal with httpie, maybe pipe the results into jq then into a file.
136 128
137 129
<video
138 -
	autoPlay
139 -
	muted
140 -
	loop
141 -
	playsinline
142 -
	className="aspect-video w-full"
143 -
	src="https://files.stevedylan.dev/Qmc1Lfd6KmrWSU1kTtrnT59nyPx1QpTvhEzPY9taagWCuv.mp4"
130 +
  autoPlay
131 +
  muted
132 +
  loop
133 +
  playsinline
134 +
  className="aspect-video w-full"
135 +
  src="/blog-images/files-stevedylan-dev/Qmc1Lfd6KmrWSU1kTtrnT59nyPx1QpTvhEzPY9taagWCuv.mp4"
144 136
></video>
145 137
146 138
This is the flexibility of a terminal based workflow that is hard to replicate on something like VSCode or Zed. It’s not even an editor issue in my opinion: it’s a development environment issue. Do code editors like VSCode take out out of that environment? Sorta, not totally, but it’s definitely not the same.
src/content/post/arc-internet.mdx +47 −52
3 3
publishDate: "08 Mar 2023"
4 4
description: "How the Arc web browser is paving the way for the future of consumer computers"
5 5
tags: ["arc", "web browsers", "tech philosophy", "internet"]
6 -
ogImage: "https://res.cloudinary.com/df9dofjus/image/upload/v1678385122/arc-browser-blog-post/opluqtxq1ceoigepyjwf.png"
6 +
ogImage: "/blog-images/cloudinary/v1678385122/arc-browser-blog-post/opluqtxq1ceoigepyjwf.png"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 -
11 -
<Image
12 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/v1678295026/arc-browser-blog-post/s3udhcext52umxrydoum.png"
13 -
	alt="Arc Logo"
14 -
	width={1920}
15 -
	height={1080}
16 -
	aspectRatio={16 / 9}
9 +
![Arc Logo](/blog-images/cloudinary/v1678385122/arc-browser-blog-post/opluqtxq1ceoigepyjwf.png)
10 +
height={1080}
11 +
aspectRatio={16 / 9}
17 12
/>
18 13
19 14
## 20 Years of the Same Thing
22 17
23 18
## Tabs, Folders, and Spaces
24 19
25 -
<Image
26 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678294127/arc-browser-blog-post/skdp4gdysro6bmmipgkb.png"
27 -
	alt="Tabs folders and spaces in arc"
28 -
	width={1920}
29 -
	height={1080}
30 -
	aspectRatio={16 / 9}
20 +
<img
21 +
  src="/blog-images/placeholder.png"
22 +
  alt="Tabs folders and spaces in arc"
23 +
  width={1920}
24 +
  height={1080}
25 +
  aspectRatio={16 / 9}
31 26
/>
32 27
33 28
One of the fundamental differences between Arc and other browsers is how it handles tabs. Instead of a row of tabs at the top of a window, Arc keeps them all in a sidebar. I genuinely believe there is a strong UX decision being made here: organization is simply easier to accomplish with a side menu than it is with a top menu. Beyond that, Arc uses a “Pinned Tabs” and “Today’s Tabs” approach to organizing your website. You may often feel hesitant to close a website because you might need it later. If you do, you can move it to the pinned tabs. When you feel like you don’t need it, then you can remove it from pinned tabs. Anything that stays in today’s tabs can be automatically cleared after a certain amount of time, or you can click a button to wipe them all out. This is powerful because it makes the decision of what is important and what isn’t easy for people to make.
34 29
35 -
<Image
36 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678294257/arc-browser-blog-post/pkslbdu6vwxmhcsfpiah.png"
37 -
	alt="View of spaces in Arc"
38 -
	width={1920}
39 -
	height={1080}
40 -
	aspectRatio={16 / 9}
30 +
<img
31 +
  src="/blog-images/placeholder.png"
32 +
  alt="View of spaces in Arc"
33 +
  width={1920}
34 +
  height={1080}
35 +
  aspectRatio={16 / 9}
41 36
/>
42 37
43 38
Of course, you may have lots of websites that you need to keep pinned, and for that, Arc has Folders and Spaces. Folders work like any other bookmark folder, where you can store as many as you want with sub-folders as well. This can be useful if you have a lot of sites that need to be referenced for a project. Folders work to a degree, but even still, you can have too many folders. That’s where spaces come in. Spaces are designed to be different workspaces for whatever you do on the internet. You could have a shopping space, an entertainment space, a developer space, anything really. You can customize the appearance, name, icon, and of course, the pinned tabs and folders for each one. Arc makes it simple to swipe between spaces and re-organize them to fit your needs.
44 39
45 -
<Image
46 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/v1678294333/arc-browser-blog-post/c3rjxfzg9fd2f5wjkdje.png"
47 -
	alt="View of favorites in Arc"
48 -
	width={912}
49 -
	height={528}
40 +
<img
41 +
  src="/blog-images/placeholder.png"
42 +
  alt="View of favorites in Arc"
43 +
  width={912}
44 +
  height={528}
50 45
/>
51 46
52 47
Favorites are special in that they are in nice little squares are the top of the sidebar no matter what space you are in, so for instance if you use Spotify a lot then that would be an ideal favorite app or website. They also have a preview feature when you hover over them for selective sites, e.g. Google Calendar will show you a brief schedule window.
55 50
56 51
## Split View
57 52
58 -
<Image
59 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678294406/arc-browser-blog-post/riqyizem3k19rq7gjcot.png"
60 -
	alt="Split view in Arc"
61 -
	height={1080}
62 -
	width={1920}
63 -
	aspectRatio={16 / 9}
53 +
<img
54 +
  src="/blog-images/placeholder.png"
55 +
  alt="Split view in Arc"
56 +
  height={1080}
57 +
  width={1920}
58 +
  aspectRatio={16 / 9}
64 59
/>
65 60
66 61
Another killer feature on Arc is split views. With a few clicks, you can easily have side-by-side panels of two different websites. You can even pin these dual tabs if you have to reference them quickly. You can actually do more than just two; depending on how big your screen is, you can go crazy! Personally, I use this all the time when reading an article while taking notes or moving information from one app to another. The Arc team recently released vertical splits as well, perhaps enough persuasion to get a vertical monitor.
69 64
70 65
## Peek View
71 66
72 -
![Peek view in Arc](https://res.cloudinary.com/df9dofjus/image/upload/v1678295158/arc-browser-blog-post/pcu6lczn2z48pvmuucte.gif)
67 +
![Peek view in Arc](/blog-images/cloudinary/v1678295158/arc-browser-blog-post/pcu6lczn2z48pvmuucte.gif)
73 68
74 69
Peek is a new feature for Arc but perhaps one of my favorites. When you click on a link, instead of taking you to a new tab or taking you off the current site, a smaller window pops up and allows you to see the content, where you can either close it or expand it into a full tab. You could be browsing Twitter, see someone reference an article, take a quick peek, and be done! It's a great way to reduce tab clutter, with a world-class UX.
75 70
76 71
## Easels
77 72
78 -
<Image
79 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678295775/arc-browser-blog-post/jwihj6ivleo9hx90ikag.png"
80 -
	alt="Easels in Arc"
81 -
	height={1080}
82 -
	width={1920}
83 -
	aspectRatio={16 / 9}
73 +
<img
74 +
  src="/blog-images/placeholder.png"
75 +
  alt="Easels in Arc"
76 +
  height={1080}
77 +
  width={1920}
78 +
  aspectRatio={16 / 9}
84 79
/>
85 80
86 81
Easels are something completely unique to Arc and have some pretty impressive abilities. The concept is like internet chalkboards. You can snip pieces of websites, add them to an easel, then add other things like your images, text, shapes, etc. Arc provides a great shortcut (that can be customized), where all you have to do is hold down Command and Shift, then click & drag across the screen to select what you want to snip. Then it will prompt what easel you want to add the snippet to, or if you want to make a new one. Easels can also be made public and shared with other people!
87 82
88 -
![Live mode in Arc Easels](https://res.cloudinary.com/df9dofjus/image/upload/v1678295978/arc-browser-blog-post/dqk3g3sfyp34ooosaupn.gif)
83 +
![Live mode in Arc Easels](/blog-images/cloudinary/v1678295978/arc-browser-blog-post/dqk3g3sfyp34ooosaupn.gif)
89 84
90 85
The craziest part? They feature a LIVE mode. Let’s say you take a snippet of the weather of your hometown from Google and save it to your easel. At any time, you can visit the easel and press the “play” button, and Arc will update the easel in real-time! I’ve used this feature to track packages and share research with my team. Others have used it to make personalized dashboards to feature live info. You really need to try it for yourself!
91 86
92 87
## Boosts
93 88
94 -
<Image
95 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678296116/arc-browser-blog-post/ow0z8neu9fnv3j21uor0.png"
96 -
	alt="A view of the Boosts menu in Arc"
97 -
	height={1080}
98 -
	width={1920}
99 -
	aspectRatio={16 / 9}
89 +
<img
90 +
  src="/blog-images/placeholder.png"
91 +
  alt="A view of the Boosts menu in Arc"
92 +
  height={1080}
93 +
  width={1920}
94 +
  aspectRatio={16 / 9}
100 95
/>
101 96
102 97
Arc has an affinity to bring back the excitement of the internet from the 90s, both in design and in customization. Not only does Arc feature themes for your browser to personalize your experience, they give you the ability to do “Boosts” to websites. Boosts are simply CSS or JS injections to websites so you can customize the color of a website or give it additional functionality.
103 98
104 -
<Image
105 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678296633/arc-browser-blog-post/m0kdu35asmm6p43t7ml8.png"
106 -
	alt="Boost being made in Arc for Tiwtter"
107 -
	height={1080}
108 -
	width={1920}
109 -
	aspectRatio={16 / 9}
99 +
<img
100 +
  src="/blog-images/placeholder.png"
101 +
  alt="Boost being made in Arc for Tiwtter"
102 +
  height={1080}
103 +
  width={1920}
104 +
  aspectRatio={16 / 9}
110 105
/>
111 106
112 107
As someone who is obsessed with customizing the personal computer environment, I absolutely love Boosts. [Nord](https://nordtheme.com/) is one of my favorite color palettes, and I use it for as many ports as I can. With Boosts, I’ve been able to customize all my regular sites to give it some flare. This comes a bit naturally for me since I have some web development experience and know how to snag CSS selectors easily, however, there are possibilities for Boosts to become a marketplace feature where non-technical users could save and use Boosts made by others.
src/content/post/base-may-not-be-for-me.mdx +4 −4
3 3
publishDate: "24 Apr 2025"
4 4
description: "My history, experience, concerns, and hope for Base as a viable L2 solution"
5 5
tags: ["base", "blockchain", "ethereum"]
6 -
ogImage: "https://files.stevedylan.dev/bafkreigz4xll63ejcmn7746hyhruik6uxneglaohbxzpcqorhmm5tvn6qu.jpg"
6 +
ogImage: "/blog-images/files-stevedylan-dev/bafkreigz4xll63ejcmn7746hyhruik6uxneglaohbxzpcqorhmm5tvn6qu.jpg"
7 7
---
8 8
9 -
![cover](https://files.stevedylan.dev/bafkreigz4xll63ejcmn7746hyhruik6uxneglaohbxzpcqorhmm5tvn6qu.jpg)
9 +
![cover](/blog-images/files-stevedylan-dev/bafkreigz4xll63ejcmn7746hyhruik6uxneglaohbxzpcqorhmm5tvn6qu.jpg)
10 10
11 11
A couple of weeks ago I watched [Vitalik: An Ethereum Story](https://ethereumfilm.xyz) with my wife, and if you haven't watched it yet I would highly recommend it. The creators did such a good job documenting the life of Ethereum, its creator Vitalik, and the core principles and values surrounding it. A wave of nostalgia took over me as I recalled the feelings I experienced when I watched my first smart contract go live on Rinkeby; a simple and very inefficient store of "posts" like a guestbook. When I saw messages recorded onchain show up on my humble website, I suddenly realized why blockchain and more specifically Ethereum could change everything. It's why I decided to dive into this field of work and why I'm still passionate about it today.
12 12
24 24
25 25
The push of coins is an attempt to rebrand tokens to be just ordinary content. The idea is that instead of posting a photo on socials, turn it into a coin. Every coin can have a photo associated with it, so why not? Instead of "liking" a post you can buy a certain amount. Zora was the first platform making this push as they pivoted from standard NFT posts to coins. I'm not here to convince you whether coins are good for the space or not. Experimentation is part of building onchain and it's how we move forward. The true controversy in my opinion is how Base has been pushing this narrative.
26 26
27 -
![base coins](https://files.stevedylan.dev/image.png)
27 +
![base coins](/blog-images/files-stevedylan-dev/image.png)
28 28
29 29
Protocols and tools are built to solve problems. In the case of Ethereum it created a sandbox where the possibilities are endless. Of course Vitalik and the creators had motives for building it, primarily to help democratize internet infrastructure and promote individual autonomy. Outside of some general principles that they believed in, the team was pretty hands off. It was up to the people to decide what they would build. They built things like decentralized governance, tokens, loans, etc. People saw the ways our internet society was broken and came up with solutions only possible because of blockchain and Ethereum. The Ethereum foundation or it's founders don't push people to use it in a certain way, they just provide the tools and let the builders build.
30 30
31 -
![base image](https://files.stevedylan.dev/bafkreifip5jfzeaha4xpankxyhllsixijxz3vvvxjxaahxeuz4lrrpyqpq.jpg)
31 +
![base image](/blog-images/files-stevedylan-dev/bafkreifip5jfzeaha4xpankxyhllsixijxz3vvvxjxaahxeuz4lrrpyqpq.jpg)
32 32
33 33
My issue with Base and the coin narrative is that it's pushing a particular agenda and use-case of blockchain. In their opinion it’s how they'll solve the issues they see in the web2 creator economy. That's a big opinion to carry and push upon your followers, and I would argue that it violates the relationship between a blockchain organization and its users. Coins are an interesting experiment, but they're also being pushed by a few individual companies, some that so happen to be financially invested by Coinbase. I want to assume the best of all parties involved, but when it comes to money, capital, and how some people can get absolutely wiped by tokens, the hair on my neck stands up. Vitalik once wrote about what he sees as the values of Ethereum but also the blockchain community at large, and he included the following:
34 34
src/content/post/beginners-guide-to-ipfs.mdx +3 −16
3 3
publishDate: "07 Oct 2022"
4 4
description: "Storage ain’t sexy, but if web3 is gonna take a leap, it's one of the biggest problems we need to solve for."
5 5
tags: ["tech philosophy", "web3", "ipfs", "nfts"]
6 -
ogImage: "https://assets-global.website-files.com/6171adb6a942ed69f5e6b5ee/62fd1b0820188c4271c6f5ac_Thumbnail1.png"
6 +
ogImage: "/blog-images/other/62fd1b0820188c4271c6f5ac_Thumbnail1.png"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 9
import bueno from "../../assets/bueno.png";
11 10
import OutLinkButton from "../../components/OutLinkButton.astro";
12 11
34 33
35 34
The traditional way of sharing files over the internet is through centralized servers. For example, if you write up and send a Tweet, the data is sent to Twitter’s servers. Then when other users want to see that Tweet, the servers pass that information to them. It’s a direct up and down motion.
36 35
37 -
<Image
38 -
	src="https://assets-global.website-files.com/6171adb6a942ed69f5e6b5ee/62fe4cfc48a5e05d952dd2c2_IPFS.png"
39 -
	alt="bueno graphic"
40 -
	width={1920}
41 -
	height={1080}
42 -
	aspectRatio={9 / 16}
43 -
/>
36 +
![image](/blog-images/placeholder.png)>
44 37
45 38
Rather than straight up and down, IPFS is up, down, and side to side. IPFS is a decentralized network of nodes that share content with each other. If we imagine a Twitter run on IPFS, every user would have their own IPFS node that would upload data to the network, and as it is requested by other users, the data is passed from node to node. Rather than one company holding and owning that data, everyone owns that data. IPFS gives users the ability to own their data.
46 39
47 -
<Image
48 -
	src="https://assets-global.website-files.com/6171adb6a942ed69f5e6b5ee/62fe4d182d482c2b9c29f199_IPFS-1.png"
49 -
	alt="ipfs node graphic"
50 -
	height={1080}
51 -
	width={1920}
52 -
	aspectRatio={16 / 9}
53 -
/>
40 +
![image](/blog-images/placeholder.png)>
54 41
55 42
## How does IPFS protect your NFTs?
56 43
src/content/post/building-a-guestbook-with-pglite-clerk-and-pinata.mdx +2 −10
3 3
publishDate: "24 Sep 2024"
4 4
description: "A quick walkthough of how I built a guestbook for my website"
5 5
tags: ["programming", "developer tools", "pinata"]
6 -
ogImage: "https://files.stevedylan.dev/QmU4XNzvRej9soBFdShhSb3KiTpN45hziDCbzdc5hBW1Nk.webp"
6 +
ogImage: "/blog-images/files-stevedylan-dev/QmU4XNzvRej9soBFdShhSb3KiTpN45hziDCbzdc5hBW1Nk.webp"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 -
11 -
<Image
12 -
	src="https://files.stevedylan.dev/QmU4XNzvRej9soBFdShhSb3KiTpN45hziDCbzdc5hBW1Nk.webp"
13 -
	alt="header image"
14 -
	height={1080}
15 -
	width={1920}
16 -
	aspectRatio={9 / 16}
17 -
/>
9 +
![image](/blog-images/placeholder.png)>
18 10
19 11
When I was first getting started in web development I remember seeing someone's website and was immediately impressed by one thing: a guestbook. You could sign in with Github and leave a message, similar to someone's Facebook wall back in the day. I thought that was the coolest thing but had no idea how to build it. Fast forward to this weekend, I was reminded how cool that was and I decided to build it for my own website.
20 12
src/content/post/building-snippets-so.mdx +4 −18
5 5
tags: ["programming", "next", "ipfs", "product"]
6 6
---
7 7
8 -
import { Image } from "astro:assets";
9 -
10 -
<Image
11 -
	src="https://www.snippets.so/og.png"
12 -
	alt="snippets-logo"
13 -
	width={1920}
14 -
	height={1080}
15 -
	aspectRatio={16 / 9}
16 -
/>
8 +
![image](/blog-images/placeholder.png)>
17 9
18 10
"I don't know why this isn't working" is a question I get often, and I usually respond with "could you share your code with me?" Next thing you know I get a cell phone image of someone's fingerprint covered laptop screen with a blur of code on it. I wish this wasn't the first time, and I certainly hope it's the last, but to be honest there was a problem.
19 11
110 102
111 103
While this library does have several themes to choose from I decided to stick with a light theme (blasphemy I know haha). A simple GitHub light theme with reduced opacity actually does a decent job. Definitely looked into trying to customize it a bit more but the way it handles syntax highlighting isn't as good as something like shiki. This might be something I look into down the road.
112 104
113 -
<Image
114 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/v1722565204/Screenshot-Arc-08-01-2024-22-17_2x_ad4w7s.png"
115 -
	alt="code editor"
116 -
	width={1920}
117 -
	height={1080}
118 -
	aspectRatio={16 / 9}
119 -
/>
105 +
![image](/blog-images/placeholder.png)>
120 106
121 107
When it comes to actually uploading the content after the user has put their code in I used the built in API routes in Next.js, simply passing in the `content` of the file, `name` if one given, and `lang` for the language used from the dropdown menu. As mentioned earlier I'm using Pinata/IPFS for uploading the content and there is a convenient API route for JSON objects, so its a simple plug and post operation.
122 108
271 257
272 258
Along with viewing the content we can also enable some fun stuff like copying it to clipboard, downloading it as a file, or sharing the snippet with a link!
273 259
274 -
![gif of share page](https://res.cloudinary.com/df9dofjus/image/upload/v1722565319/Screenshot-Arc-08-01-2024-22-24_muxrrg.gif)
260 +
![gif of share page](/blog-images/cloudinary/v1722565319/Screenshot-Arc-08-01-2024-22-24_muxrrg.gif)
275 261
276 262
## API + CLI
277 263
280 266
Request:
281 267
282 268
```bash
283 -
curl --location 'https://www.snippets.so/api/upload' \
269 +
curl --location '/blog-images/other/api/upload' \
284 270
          --header 'Content-Type: application/json' \
285 271
          --data '{
286 272
            "content": "console.log(\"hello world!\")",
src/content/post/case-for-ipfs-on-l1-chains.mdx +1 −1
3 3
publishDate: "06 Dec 2022"
4 4
description: "There are so many new blockahins appearing and they all need a solution to off-chain storage"
5 5
tags: ["web3", "ipfs", "tech philosophy"]
6 -
ogImage: "https://miro.medium.com/v2/resize:fit:4800/format:webp/1*4xA96GrA9iLYMp5vorcjyQ.jpeg"
6 +
ogImage: "/blog-images/medium/v2/resize:fit:4800/format:webp/1*4xA96GrA9iLYMp5vorcjyQ.jpeg"
7 7
---
8 8
9 9
import medium from "../../assets/medium.png";
src/content/post/how-sp1-precompiles-revolutionized-zkvm-performance.mdx +2 −10
3 3
publishDate: "27 Oct 2024"
4 4
description: "Learn about Succinct, SP1, and how the innovation of Precompiles changed the zkVM space"
5 5
tags: ["zk", "succinct", "precompiles", "zkVM"]
6 -
ogImage: "https://files.stevedylan.dev/bafybeicrnbi7yt5wcnaiielxhvb5fb7mocl7k2ub43e3nqwlnfev4zekkm.webp"
6 +
ogImage: "/blog-images/files-stevedylan-dev/bafybeicrnbi7yt5wcnaiielxhvb5fb7mocl7k2ub43e3nqwlnfev4zekkm.webp"
7 7
hidden: true
8 8
---
9 9
10 -
import { Image } from "astro:assets";
11 -
12 -
<Image
13 -
	src="https://files.stevedylan.dev/bafybeicrnbi7yt5wcnaiielxhvb5fb7mocl7k2ub43e3nqwlnfev4zekkm.webp"
14 -
	alt="header image"
15 -
	height={1080}
16 -
	width={1920}
17 -
	aspectRatio={9 / 16}
18 -
/>
10 +
![image](/blog-images/placeholder.png)>
19 11
20 12
In the last few years the terms "zk" or "zero knowledge proofs" have been buzzing and for good reason. It's a relatively new piece of tech in cryptography that allows someone to prove something without revealing the information itself, which has massive implications for not only the blockchain space but for privacy and trusted code. While zk's are powerful, they also come at a cost. The majority of real-world use zero knowledge proofs are computationally expensive and inefficient, and in order to truly scale and make a difference, the cost needs to come down. This is where [Succinct](https://succinct.xyz/) comes in, a company that specializes in zkVM technology and makes it accessible to developers. Not only has Succinct built [SP1](https://blog.succinct.xyz/sp1-is-live/), an zkVM that allows you to write zero knowledge proofs in Rust, but they have also revolutionized efficiency with Precompiles.
21 13
src/content/post/how-to-create-a-weekly-photo-zine.mdx +12 −73
3 3
publishDate: "08 June 2023"
4 4
description: "Learn how to use ChatGPT and Pinata to make a custom web photo zine with zero developer experience"
5 5
tags: ["web development", "ai", "chatgpt"]
6 -
ogImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/1*2Ym-4FvWAD65UiaTVZ67uA@2x.png"
6 +
ogImage: "/blog-images/medium/v2/resize:fit:1400/format:webp/1*2Ym-4FvWAD65UiaTVZ67uA@2x.png"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 9
import medium from "../../assets/medium.png";
11 10
import OutLinkButton from "../../components/OutLinkButton.astro";
12 11
24 23
25 24
First, you’ll want to sign up for an account with Pinata. I would recommend choosing the Picnic plan for this project as we will utilize its speed and flexibility for hosting our content. Once you have an account, you’ll want to start uploading all your images to be used for the zine. In this demo I’ve selected some of my favorite images from a trip I took a few years ago.
26 25
27 -
<Image
28 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*DIwVzCaXQliVmZNmaBmlDQ@2x.png"
29 -
	alt="Pinata Add Files"
30 -
	width={1920}
31 -
	height={1080}
32 -
	aspectRatio={16 / 9}
33 -
/>
26 +
![image](/blog-images/placeholder.png)>
34 27
35 28
Once you have your content uploaded, you can click on the preview button to see your photo. Copy the URL for that image and paste it somewhere for later and repeat the process for however much content you want to put in your zine.
36 29
37 -
<Image
38 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*fOoOKMn_texWENXvLbyUwg@2x.png"
39 -
	alt="Pinata Preview"
40 -
	width={1920}
41 -
	height={1080}
42 -
	aspectRatio={16 / 9}
43 -
/>
30 +
![image](/blog-images/placeholder.png)>
44 31
45 32
## ChatGPT
46 33
53 40
54 41
It will respond with several questions, so simply provide the answers like you see below.
55 42
56 -
<Image
57 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*al2t7LEUktAmcd2qKSDikA@2x.png"
58 -
	alt="ChatGPT Response"
59 -
	height={1080}
60 -
	width={1920}
61 -
	aspectRatio={16 / 9}
62 -
/>
43 +
![image](/blog-images/placeholder.png)>
63 44
64 45
Below my last answer I went ahead and gave ChatGPT the content of text and image links, and just like that it generated the html content to start with. If it doesn’t finish generating all the code at once, you can click “continue generating” and it will keep going where it left off. Also be sure it includes all your content, make sure it knows you have zero programming knowledge and it needs to write everything on its own. Once it does this there should be a copy button at the top of the code box, so go ahead and click that:
65 46
66 -
<Image
67 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*vWdVJTZgck3BBA5oqHF0-g@2x.png"
68 -
	alt="ChatGPT Response"
69 -
	width={1920}
70 -
	height={1080}
71 -
	aspectRatio={16 / 9}
72 -
/>
47 +
![image](/blog-images/placeholder.png)>
73 48
74 49
## Replit
75 50
76 51
Now to actually paste our code and see what it does, we will be using an online code editor called [Replit](https://replit.com). Just sign up for a free account, and in the top right corner you will want to click “Create Repl.”
77 52
78 -
<Image
79 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*d6dOx50styHgfdzVUSkPcQ@2x.png"
80 -
	alt="New Repl"
81 -
	width={1920}
82 -
	height={1080}
83 -
	aspectRatio={16 / 9}
84 -
/>
53 +
![image](/blog-images/placeholder.png)>
85 54
86 55
After that you will want to select the HTML, CSS, and Javascript template and give your project a name.
87 56
88 -
<Image
89 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*6DZYT_oYQ-3ejp4rmRa_ZA@2x.png"
90 -
	alt="Choose repl template"
91 -
	width={1920}
92 -
	height={1080}
93 -
	aspectRatio={16 / 9}
94 -
/>
57 +
![image](/blog-images/placeholder.png)>
95 58
96 59
Once you’re in your repl project, delete out the starter HTML and paste in your own code from ChatGPT, then hit CMD/CTR + S to save it. After that you should see on the right side your project:
97 60
98 -
<Image
99 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*Ve3BV5meR4oHFcPhKmaBuw@2x.png"
100 -
	alt="Repl screen"
101 -
	width={1920}
102 -
	height={1080}
103 -
	aspectRatio={16 / 9}
104 -
/>
61 +
![image](/blog-images/placeholder.png)>
105 62
106 63
You can also change the size of your webview by clicking and dragging the pane to shrink it more towards a mobile view if that’s what you’re after.
107 64
108 65
Now your project will likely not be perfect right out of the gate, and you will want to make some changes. Since ChatGPT keeps that conversation history, you can go back and forth with it for the changes you want.
109 66
110 -
<Image
111 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*kJwlNIUnxBRselpxW8f-ig@2x.png"
112 -
	alt="ChatGPT Adjustments"
113 -
	width={1920}
114 -
	height={1080}
115 -
	aspectRatio={16 / 9}
116 -
/>
67 +
![image](/blog-images/placeholder.png)>
117 68
118 69
The key is to make sure you are precise in what changes you want, and that ChatGPT gives you the updated code afterwards. Once you’ve gotten your zine looking the way you want it, click on the little dropdown arrow next to the index.html file and click “Download.”
119 70
120 -
<Image
121 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*29kNYSjhvijup06Mi6OeOA@2x.png"
122 -
	alt="Download HTML"
123 -
	width={1920}
124 -
	height={1080}
125 -
	aspectRatio={16 / 9}
126 -
/>
71 +
![image](/blog-images/placeholder.png)>
127 72
128 73
After you have download the file, go ahead and upload it to Pinata just like you did for your image files. Then just click on that file to see your zine. You can view mine with this link:
129 74
130 75
[A Venture to the North East](https://stevedylanphoto.mypinata.cloud/ipfs/QmWAUQfKhJ19kZqcJLP6nPbHFRToiaFquGJ71gJEA4cRVT)
131 76
132 -
![Gif of zine](https://miro.medium.com/v2/resize:fit:1124/1*3XuUbuzy-CZAB-78TR-tTQ.gif)
77 +
![Gif of zine](/blog-images/medium/v2/resize:fit:1124/1*3XuUbuzy-CZAB-78TR-tTQ.gif)
133 78
134 79
## Gateway tip
135 80
136 81
Now here’s the bonus info: if you wanted to make this a weekly update, Pinata provides a pretty neat way to do that. First you would of course need to give ChatGPT your code and the update content and it could swap it out for you (or you could even take your hand at changing it in Replit!), and once you have uploaded the new index.html to Pinata, you can click on the “more” button next to the file and select “Set gateway as a root”
137 82
138 -
<Image
139 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*kZZWMcyP0tjhGDU_pCZj-w@2x.png"
140 -
	alt="Set gateway as root"
141 -
	width={1920}
142 -
	height={1080}
143 -
	aspectRatio={16 / 9}
144 -
/>
83 +
![image](/blog-images/placeholder.png)>
145 84
146 85
What this will do is redirect my base url “https://stevedylanphoto.mypinata.cloud” to the new index.html file that was uploaded. I could keep updating it with each new file every week with a new photo zine, as well as improve it over time. Of course if you don’t like the “mypinata.cloud” domain for your zine, you could very easily use your own domain like “stevephotozine.com” and it would work exactly the same. My personal photography website is stevedylanphoto.com, so I just used “zine.stevedylanphoto.com” as a dedicated domain for this zine. Check it out:
147 86
src/content/post/how-to-migrate-from-neovim-to-vscode.mdx +3 −3
23 23
24 24
VSCode is the exact opposite. There's loads of extra stuff all around the editor that's pure distraction. I mean, be real, who is using the minimap thing? What about that extra bar on the right with a bunch of icons that you'll never use? It's so aggressive that I wanted to get out as soon as I opened it. However, there are solutions to this! You can simply go to the View menu, then Appearance, and from there toggle all those nasty bits off. Now you can get VSCode looking more like this:
25 25
26 -
![gooey](https://res.cloudinary.com/df9dofjus/image/upload/v1688690521/Screenshot-Code-07-06-2023-20-19_2x_gz0arj.png)
26 +
![gooey](/blog-images/cloudinary/v1688690521/Screenshot-Code-07-06-2023-20-19_2x_gz0arj.png)
27 27
28 28
## Keybindings
29 29
43 43
44 44
Here's the sauce: discovering VSCode's API. Something I had never done before is going to the built-in keybindings menu for VSCode and looking at what was in there. There's so much there and it's a bit overwhelming, but once you figure out how to find the command you're looking for, it makes just about anything possible. For my pane switching keymap, I wanted to find which command was changing the focus. I searched "focus left" in the VSCode keybindings and sure enough, there it was: `workbench.action.focusLeftGroup`.
45 45
46 -
![Keybindings](https://res.cloudinary.com/df9dofjus/image/upload/v1688690521/Screenshot-Code-07-06-2023-20-25_2x_kl790i.png)
46 +
![Keybindings](/blog-images/cloudinary/v1688690521/Screenshot-Code-07-06-2023-20-25_2x_kl790i.png)
47 47
48 48
Now back in our VSCodeVim config, we can add that method to the binding like so.
49 49
61 61
62 62
It works like a charm. If you're doing this yourself, something I found useful is right-clicking on the command in the VSCode keybindings menu to see the method. Another example where this came in handy was using the "Find Files" command. By default, it's `Cmd + P` on Mac, but I was used to something like `<leader> + F`. When I searched "Open File..." in the keybindings, it didn't show the method like the others did. However, after right-clicking, you can click "Copy Command ID" to grab it.
63 63
64 -
![command id](https://res.cloudinary.com/df9dofjus/image/upload/v1688690521/Screenshot-Code-07-06-2023-20-53_zxnjxo.gif)
64 +
![command id](/blog-images/cloudinary/v1688690521/Screenshot-Code-07-06-2023-20-53_zxnjxo.gif)
65 65
66 66
Configuring VSCode is not the most pleasant experience, but on the plus side you can save your config file and move it wherever you want, and you don’t have to mess with configuring as many plugins with Neovim. I’m still on the fence as to whether I’ll keep trying to use VSCode or go back to Neovim, but hopefully these tips help anyone out there trying to make it work for their own personal reasons.
67 67
src/content/post/how-to-mint-on-sui.mdx +4 −23
3 3
publishDate: "27 Feb 2023"
4 4
description: "Learn how to use the Sui SDK and Pinata to mint an NFT"
5 5
tags: ["web3", "ipfs", "nfts", "tutorials", "web development"]
6 -
ogImage: "https://miro.medium.com/v2/resize:fit:4800/format:webp/1*JbX3kWI20G3EaKJNgXfQiQ.png"
6 +
ogImage: "/blog-images/medium/v2/resize:fit:4800/format:webp/1*JbX3kWI20G3EaKJNgXfQiQ.png"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 9
import medium from "../../assets/medium.png";
11 10
import OutLinkButton from "../../components/OutLinkButton.astro";
12 11
28 27
29 28
Getting started with Pinata is easy! Just visit the signup page here and start out with a free account. Now all you have to do is upload the image you want to use. Do that by visiting the main files page and clicking “Upload” and “Select File.”
30 29
31 -
<Image
32 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*TF200pv3qCMx41dHZoE_kg@2x.png"
33 -
	alt="pinata files page"
34 -
	height={1080}
35 -
	width={1920}
36 -
	aspectRatio={16 / 9}
37 -
/>
30 +
![image](/blog-images/placeholder.png)>
38 31
39 32
After that just follow the steps of selecting your file, give it a name, then upload! Once done it should show up in your files page as seen below, and we will want to copy the CID for later.
40 33
41 -
<Image
42 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*68MMViE3btD8hHuxaTbEzg@2x.png"
43 -
	alt="pinata files page"
44 -
	height={1080}
45 -
	width={1920}
46 -
	aspectRatio={16 / 9}
47 -
/>
34 +
![image](/blog-images/placeholder.png)>
48 35
49 36
## Code Setup with the Sui JS SDK
50 37
347 334
348 335
If all works as it should you’ll get a link and then you should see your final NFT!
349 336
350 -
<Image
351 -
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*oCtNaZK3LOx807tA8IEldg@2x.png"
352 -
	alt="pinata files page"
353 -
	width={1920}
354 -
	height={1080}
355 -
	aspectRatio={16 / 9}
356 -
/>
337 +
![image](/blog-images/placeholder.png)>
357 338
358 339
## You did it!! 🎉
359 340
src/content/post/how-to-offset-NFT-emissions.mdx +5 −24
3 3
publishDate: "22 Apr 2022"
4 4
description: "Learn how Aerial is helping make NFTs carbon neutral with their emissions API"
5 5
tags: ["web3", "nfts", "tutorials", "web development", "tech philosophy"]
6 -
ogImage: "https://miro.medium.com/v2/resize:fit:4800/format:webp/1*KxoVDEZFH3mJrlfeguMYjg.jpeg"
6 +
ogImage: "/blog-images/medium/v2/resize:fit:4800/format:webp/1*KxoVDEZFH3mJrlfeguMYjg.jpeg"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 9
import medium from "../../assets/medium.png";
11 10
import OutLinkButton from "../../components/OutLinkButton.astro";
12 11
20 19
21 20
While I love dooting around on my computer, I also love the outdoors. I love hiking, seeing mountains, hearing rivers, and smelling wildflowers. I love hearing birds in the morning and geese at night. I love to watch my son experience nature and get excited over a butterfly. So yeah, it kinda kills me to think of how this wonderful industry of crypto can harm this amazing planet.
22 21
23 -
<Image
24 -
	src="https://cdn-images-1.medium.com/max/2400/1*xQEP67xsPsQ4vU7wjKLFBw.png"
25 -
	alt="ethereum carbon emissions chart"
26 -
	width={1920}
27 -
	height={1080}
28 -
	aspectRatio={16 / 9}
29 -
/>
22 +
![image](/blog-images/placeholder.png)>
30 23
31 24
The energy consumed by proof of work blockchains is staggering. It’s no secret, and if we want to enjoy this planet we need to accept it. I know I feel defensive when someone attacks something I enjoy, but it helps to take a deep breath and ask the big questions: “could I be wrong?” If crypto is putting the planet in danger, what do we do?
32 25
40 33
<iframe src="https://www.aerial.is/nft/embed?address=0x3e88721fa41d5e102d54b4a04e550222efdd234d">
41 34
```
42 35
43 -
<Image
44 -
	src="https://cdn-images-1.medium.com/max/2000/1*IPAefM4T_NiRygdri_DzhA.png"
45 -
	alt="screenshot of widget"
46 -
	width={1920}
47 -
	height={1080}
48 -
	aspectRatio={6 / 2}
49 -
/>
36 +
![image](/blog-images/placeholder.png)>
50 37
51 38
In this post we’ll take their API a step further and build our own custom widget. We can use it in our frontend website that displays more details about the project’s emissions with the goal of making visitors more aware!
52 39
193 180
194 181
If we run the app and check the dev console, we can see our data!
195 182
196 -
<Image
197 -
	src="https://cdn-images-1.medium.com/max/2000/1*UrdopTgDhErJObLI3V8DwQ.png"
198 -
	alt="dev tools"
199 -
	width={1920}
200 -
	height={1080}
201 -
	aspectRatio={16 / 4}
202 -
/>
183 +
![image](/blog-images/placeholder.png)>
203 184
204 185
Now that we have the data, it’s as simple as displaying it so users on our website can see it!
205 186
522 503
523 504
Here is a quick preview of our final project
524 505
525 -
![gif](https://cdn-images-1.medium.com/max/2000/1*4tyAgdhStdoWa-aofwGgBQ.gif)
506 +
![gif](/blog-images/medium/max/2000/1*4tyAgdhStdoWa-aofwGgBQ.gif)
526 507
527 508
You can actually view the working component live [here](https://aerial-component.vercel.app/), and you can freely download the repo on [GitHub](https://github.com/stevedsimkins/aerial-component)!
528 509
src/content/post/learning-rust-with-ai.mdx +3 −3
3 3
publishDate: "11 Jul 2025"
4 4
description: "A glimpse into a better way of learning to code, where you put the LLM in the backseat while you drive"
5 5
tags: ["ai", "developer tools", "rust"]
6 -
ogImage: "https://files.stevedylan.dev/ai-mentor.png"
6 +
ogImage: "/blog-images/files-stevedylan-dev/ai-mentor.png"
7 7
---
8 8
9 -
![cover](https://files.stevedylan.dev/ai-mentor.png)
9 +
![cover](/blog-images/files-stevedylan-dev/ai-mentor.png)
10 10
11 11
I think most developers out there would agree that we're in a bit of an AI hype bubble, yet one piece of AI tech that has recently hit the market which developers can't stop talking about might be different. Of course I'm talking about "agents" like Claude Code or my personal favorite [Opencode](https://opencode.ai). With a few prompts you can have an AI create a plan to implement a feature or fix a bug, and it will just do it. They generally have deep system integration with your terminal and LSPs to have a great understanding of how to build something. However in my opinion these tools, like any other AI tool, are a double edged sword.
12 12
53 53
54 54
When all was said and done, I accomplished my goal of building a fun little CLI called [walletfetch](https://github.com/stevedylandev/walletfetch) that works like Neofetch but for EVM based wallets!
55 55
56 -
![walletfetch](https://files.stevedylan.dev/walletfetch.png)
56 +
![walletfetch](/blog-images/files-stevedylan-dev/walletfetch.png)
57 57
58 58
Taking the time to learn this way was super helpful, and I will definitely be doing it more often. It's certainly not perfect, and it's no replacement for core materials like the Rust book, but in my opinion it's a great way to build projects and learn something new. If you're an aspiring developer, I cannot stress this enough: be competent. Always be curious, ask questions, figure out "why," and look to solve problems you really care about. I saw a post on X recently that went something like "quitting software development now due to AI tools is like quitting woodworking because the table saw was invented." It's a tool like anything else, but you've got to know your fundamentals, and you've got to know what to build. Be a woodworker. Be a craftsman.
59 59
src/content/post/leaving-neovim-for-zed.mdx +118 −138
3 3
publishDate: "16 Aug 2024"
4 4
description: "A journey through text editors and how I landed on Zed after years of Neovim"
5 5
tags: ["programming", "developer tools", "neovim", "zed"]
6 -
ogImage: "https://files.stevedylan.dev/leaving-neovim-for-zed.png"
6 +
ogImage: "/blog-images/files-stevedylan-dev/leaving-neovim-for-zed.png"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 -
11 -
<Image
12 -
	src="https://files.stevedylan.dev/leaving-neovim-for-zed.png"
13 -
	alt="header image"
14 -
	height={1080}
15 -
	width={1920}
16 -
	aspectRatio={9 / 16}
17 -
/>
9 +
![header image](/blog-images/files-stevedylan-dev/leaving-neovim-for-zed.png)
18 10
19 11
I think every developer has their own text editor journey and how they landed on the tool they use today. Perhaps I'm a geek but I love those stories. I have a great appreciation for developer tools and the work that goes into them. This post is for the other geeks out there that also care, and I hope my journey and perspective can prompt others to experiment and try developer tools outside their comfort zones. You never know what you might land on and how much you might enjoy it!
20 12
40 32
41 33
One of the biggest things that has stood out to me using Zed so far is how “everything just works.” There are so many features of an IDE or text editor that people take for granted until they have to set it up themselves in something lower level like Neovim. LSP (language server protocol) is certainly one of them. If you’re not familiar it’s the hints or errors that show up while you’re writing up your code, giving you deep insights to your repo on a language level. When you setup LSP in Neovim it’s a lot of work, and sometimes it can be a bit harder to figure out why it might be bugging out. However it does give you way more control and the option to do a lot of customization. With Zed LSP just works. There are configurations you can make to edit some things, but as a whole it just zips out of the box. There are already keybindings for things like “show definition”, “go to definition”, or even code actions. The only downside is outside of an extension you can’t use your own LSP that’s installed on your machine, but there’s always a pretty large language support that I haven’t had this issue yet.
42 34
43 -
<Image
44 -
	src="https://files.stevedylan.dev/QmNkysZ5Roy723sphUST8abmHakKR86K2YHXeeVTU22ASH.png"
45 -
	alt="header image"
46 -
	height={1080}
47 -
	width={1920}
48 -
	aspectRatio={9 / 16}
49 -
/>
35 +
![LSP demo](/blog-images/files-stevedylan-dev/QmNkysZ5Roy723sphUST8abmHakKR86K2YHXeeVTU22ASH.png)
50 36
51 37
Another piece that’s related to LSP is completions. This is when you’re typing some code and get suggestions for auto completions that quickly fill the rest of the code out. LSPs usually have great auto-completion because they’re aware of the patterns used in that language. Just to be clear we’re not talking about Copilot yet, this is just completions for snippets and LSP. Once again with Zed it just works out of the box, unlike Neovim which ends up requiring several plugins to make it work right.
52 38
53 39
<video
54 -
	autoPlay
55 -
	muted
56 -
	loop
57 -
	playsinline
58 -
	className="aspect-video w-full"
59 -
	src="https://files.stevedylan.dev/QmVEqwPxAoWgDwCLM3PrVxseLtLjCDK8LMxxz6DyD5fjxz.mp4"
40 +
  autoPlay
41 +
  muted
42 +
  loop
43 +
  playsinline
44 +
  className="aspect-video w-full"
45 +
  src="/blog-images/files-stevedylan-dev/QmVEqwPxAoWgDwCLM3PrVxseLtLjCDK8LMxxz6DyD5fjxz.mp4"
60 46
></video>
61 47
62 48
Finally there’s Git integrations. What normally required multiple plugins in Neovim is again ready out of the box with Zed, including feature like toggling Git Blame, viewing diffs, and gutter symbols showing the status of edited lines.
63 49
64 50
<video
65 -
	autoPlay
66 -
	muted
67 -
	loop
68 -
	playsinline
69 -
	className="aspect-video w-full"
70 -
	src="https://files.stevedylan.dev/Qmcqv6kXnHHVbX8RxWpK6coPZHyNcPU7bxPfZ2Zwbsm6bz.mp4"
51 +
  autoPlay
52 +
  muted
53 +
  loop
54 +
  playsinline
55 +
  className="aspect-video w-full"
56 +
  src="/blog-images/files-stevedylan-dev/Qmcqv6kXnHHVbX8RxWpK6coPZHyNcPU7bxPfZ2Zwbsm6bz.mp4"
71 57
></video>
72 58
73 59
If I had to make a crude comparison, it’s similar to Linux and Apple. Linux will give you far more control over every piece of your software and hardware at the cost of spending time to configure it. Apple will give you less control but it will likely run smoother.
95 81
Zed also features an assistant panel where you can access several AI models via API, including OpenAI, Ollama, and Anthropic. Just requires a few lines of config to get started.
96 82
97 83
<video
98 -
	autoPlay
99 -
	muted
100 -
	loop
101 -
	playsinline
102 -
	className="aspect-video w-full"
103 -
	src="https://files.stevedylan.dev/QmPo8nXP8w9hJfxnhCpggrGKP89VbtiV1Rf3mFRHj9fUqA.mp4"
84 +
  autoPlay
85 +
  muted
86 +
  loop
87 +
  playsinline
88 +
  className="aspect-video w-full"
89 +
  src="/blog-images/files-stevedylan-dev/QmPo8nXP8w9hJfxnhCpggrGKP89VbtiV1Rf3mFRHj9fUqA.mp4"
104 90
></video>
105 91
106 92
One feature that I think is particularly nice is the inline assistant, where you can select some lines of code and use `ctrl-enter` to trigger a request to be made to your code via the AI assistance configuration mentioned previously. If you like the results then you can confirm and keep coding.
107 93
108 94
<video
109 -
	autoPlay
110 -
	muted
111 -
	loop
112 -
	playsinline
113 -
	className="aspect-video w-full"
114 -
	src="https://files.stevedylan.dev/QmTTGznMRr64oqJG7hXFiCrqrTqXiY3kaPuQnhWbCCSAaL.mp4"
95 +
  autoPlay
96 +
  muted
97 +
  loop
98 +
  playsinline
99 +
  className="aspect-video w-full"
100 +
  src="/blog-images/files-stevedylan-dev/QmTTGznMRr64oqJG7hXFiCrqrTqXiY3kaPuQnhWbCCSAaL.mp4"
115 101
></video>
116 102
117 103
### Zed ≠ Neovim
128 114
129 115
```json keymap.json
130 116
[
131 -
	{
132 -
		"context": "Editor && VimControl && !VimWaiting && !menu",
133 -
		"bindings": {
134 -
			"space b": "editor::ToggleGitBlame",
135 -
			"shift-k": "editor::Hover",
136 -
			"space l f": "editor::Format",
137 -
			"space d": "diagnostics::Deploy",
138 -
			"space f f": "file_finder::Toggle",
139 -
			"space o": "tab_switcher::Toggle",
140 -
			"space e": "workspace::ToggleLeftDock",
141 -
			"space /": "workspace::NewSearch",
142 -
			"n": "search::SelectNextMatch",
143 -
			"shift-n": "search::SelectPrevMatch",
144 -
			"space t": "workspace::NewCenterTerminal",
145 -
			"g b": "editor::ToggleComments",
146 -
			"+ +": "workspace::Save",
147 -
			"space c": "pane::CloseActiveItem"
148 -
		}
149 -
	},
150 -
	{
151 -
		"context": "Editor && vim_mode == visual && !VimWaiting && !VimObject",
152 -
		"bindings": {
153 -
			"shift-j": "editor::MoveLineDown",
154 -
			"shift-k": "editor::MoveLineUp"
155 -
		}
156 -
	}
117 +
  {
118 +
    "context": "Editor && VimControl && !VimWaiting && !menu",
119 +
    "bindings": {
120 +
      "space b": "editor::ToggleGitBlame",
121 +
      "shift-k": "editor::Hover",
122 +
      "space l f": "editor::Format",
123 +
      "space d": "diagnostics::Deploy",
124 +
      "space f f": "file_finder::Toggle",
125 +
      "space o": "tab_switcher::Toggle",
126 +
      "space e": "workspace::ToggleLeftDock",
127 +
      "space /": "workspace::NewSearch",
128 +
      "n": "search::SelectNextMatch",
129 +
      "shift-n": "search::SelectPrevMatch",
130 +
      "space t": "workspace::NewCenterTerminal",
131 +
      "g b": "editor::ToggleComments",
132 +
      "+ +": "workspace::Save",
133 +
      "space c": "pane::CloseActiveItem"
134 +
    }
135 +
  },
136 +
  {
137 +
    "context": "Editor && vim_mode == visual && !VimWaiting && !VimObject",
138 +
    "bindings": {
139 +
      "shift-j": "editor::MoveLineDown",
140 +
      "shift-k": "editor::MoveLineUp"
141 +
    }
142 +
  }
157 143
]
158 144
```
159 145
161 147
162 148
```json keymap.json
163 149
[
164 -
	{
165 -
		"context": "Dock || Terminal || Editor",
166 -
		"bindings": {
167 -
			"ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
168 -
			"ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
169 -
			"ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
170 -
			"ctrl-j": ["workspace::ActivatePaneInDirection", "Down"]
171 -
		}
172 -
	}
150 +
  {
151 +
    "context": "Dock || Terminal || Editor",
152 +
    "bindings": {
153 +
      "ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
154 +
      "ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
155 +
      "ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
156 +
      "ctrl-j": ["workspace::ActivatePaneInDirection", "Down"]
157 +
    }
158 +
  }
173 159
]
174 160
```
175 161
181 167
182 168
```json settings.json
183 169
{
184 -
	"cursor_blink": false,
185 -
	"relative_line_numbers": true,
186 -
	"scrollbar": {
187 -
		"show": "never"
188 -
	},
189 -
	"vertical_scroll_margin": 0,
190 -
	"tab_bar": {
191 -
		"show": false
192 -
	},
193 -
	"toolbar": {
194 -
		"breadcrumbs": true,
195 -
		"quick_actions": false
196 -
	}
170 +
  "cursor_blink": false,
171 +
  "relative_line_numbers": true,
172 +
  "scrollbar": {
173 +
    "show": "never"
174 +
  },
175 +
  "vertical_scroll_margin": 0,
176 +
  "tab_bar": {
177 +
    "show": false
178 +
  },
179 +
  "toolbar": {
180 +
    "breadcrumbs": true,
181 +
    "quick_actions": false
182 +
  }
197 183
}
198 184
```
199 185
200 -
<Image
201 -
	src="https://files.stevedylan.dev/QmVtqSNc6Ff9Zy8vBxr92AcVdh9eAGYkU8zWGLoyTnoeDL.png"
202 -
	alt="header image"
203 -
	height={1080}
204 -
	width={1920}
205 -
	aspectRatio={9 / 16}
206 -
/>
186 +
![Zed settings](/blog-images/files-stevedylan-dev/QmVtqSNc6Ff9Zy8vBxr92AcVdh9eAGYkU8zWGLoyTnoeDL.png)
207 187
208 188
### Plugin Replacements
209 189
211 191
212 192
```json keymap.json
213 193
{
214 -
	"context": "Editor && VimControl && !VimWaiting && !menu",
215 -
	"bindings": {
216 -
		"space o": "tab_switcher::Toggle"
217 -
	}
194 +
  "context": "Editor && VimControl && !VimWaiting && !menu",
195 +
  "bindings": {
196 +
    "space o": "tab_switcher::Toggle"
197 +
  }
218 198
}
219 199
```
220 200
221 201
<video
222 -
	autoPlay
223 -
	muted
224 -
	loop
225 -
	playsinline
226 -
	className="aspect-video w-full"
227 -
	src="https://files.stevedylan.dev/QmQntcJQkJKoh2bmreRYun4BfDuLb8rXry6ZFmiVYPaeRx.mp4"
202 +
  autoPlay
203 +
  muted
204 +
  loop
205 +
  playsinline
206 +
  className="aspect-video w-full"
207 +
  src="/blog-images/files-stevedylan-dev/QmQntcJQkJKoh2bmreRYun4BfDuLb8rXry6ZFmiVYPaeRx.mp4"
228 208
></video>
229 209
230 210
Speaking of Telescope, one big replacement is project wide search. While Zed doesn't have a fuzzy find feature, the project wide search is excellent. It will show all results in a multibuffer view which is pretty slick, and allows you to jump between that view and the buffer itself pretty easily.
231 211
232 212
<video
233 -
	autoPlay
234 -
	muted
235 -
	loop
236 -
	playsinline
237 -
	className="aspect-video w-full"
238 -
	src="https://files.stevedylan.dev/QmY2Cs7zBk7bEa7skNLBcA5dFnSmTS7CotFjftcMA2r3m1.mp4"
213 +
  autoPlay
214 +
  muted
215 +
  loop
216 +
  playsinline
217 +
  className="aspect-video w-full"
218 +
  src="/blog-images/files-stevedylan-dev/QmY2Cs7zBk7bEa7skNLBcA5dFnSmTS7CotFjftcMA2r3m1.mp4"
239 219
></video>
240 220
241 221
The terminal toggle is pretty similar to something like VSCode but there are some other hidden ways to get a better terminal experience. One of them is a shortcut to toggle the bottom terminal to be full screen, but even better is opening a terminal as a buffer in the main editing view.
242 222
243 223
```json
244 224
{
245 -
	"context": "Editor && VimControl && !VimWaiting && !menu",
246 -
	"bindings": {
247 -
		"space t": "workspace::NewCenterTerminal"
248 -
	}
225 +
  "context": "Editor && VimControl && !VimWaiting && !menu",
226 +
  "bindings": {
227 +
    "space t": "workspace::NewCenterTerminal"
228 +
  }
249 229
}
250 230
```
251 231
252 232
<video
253 -
	autoPlay
254 -
	muted
255 -
	loop
256 -
	playsinline
257 -
	className="aspect-video w-full"
258 -
	src="https://files.stevedylan.dev/QmYGcEqane6cpVPJj9H7qjgYmV75GPhmy7hkKob6YPVscY.mp4"
233 +
  autoPlay
234 +
  muted
235 +
  loop
236 +
  playsinline
237 +
  className="aspect-video w-full"
238 +
  src="/blog-images/files-stevedylan-dev/QmYGcEqane6cpVPJj9H7qjgYmV75GPhmy7hkKob6YPVscY.mp4"
259 239
></video>
260 240
261 241
One of the big things I had to leave behind was Tmux and switching projects. While it isn't a perfect replacement, Zed has a "switch projects" feature which works really well and makes it pretty easy to switch contexts. You just won't get the exact same control and layout setup that you can get with Tmux
262 242
263 243
```json
264 244
{
265 -
	"context": "Workspace",
266 -
	"bindings": {
267 -
		"cmd-k": [
268 -
			"projects::OpenRecent",
269 -
			{
270 -
				"create_new_window": false
271 -
			}
272 -
		]
273 -
	}
245 +
  "context": "Workspace",
246 +
  "bindings": {
247 +
    "cmd-k": [
248 +
      "projects::OpenRecent",
249 +
      {
250 +
        "create_new_window": false
251 +
      }
252 +
    ]
253 +
  }
274 254
}
275 255
```
276 256
277 257
<video
278 -
	autoPlay
279 -
	muted
280 -
	loop
281 -
	playsinline
282 -
	className="aspect-video w-full"
283 -
	src="https://files.stevedylan.dev/QmXh5mDRJyQRSCfmHNeZ5DDwnzPr3uo5mqCMQN1mLzGh6T.mp4"
258 +
  autoPlay
259 +
  muted
260 +
  loop
261 +
  playsinline
262 +
  className="aspect-video w-full"
263 +
  src="/blog-images/files-stevedylan-dev/QmXh5mDRJyQRSCfmHNeZ5DDwnzPr3uo5mqCMQN1mLzGh6T.mp4"
284 264
></video>
285 265
286 266
## Should You Use Zed?
291 271
292 272
[settings.json](https://snippets.so/snip/bafkreigrqomoj4se5p25hy3vv5p52xuokuwpsclwhqe6cpxoy6vnyfdlzq)
293 273
294 -
[keymap.json](https://www.snippets.so/snip/bafkreieaxwbdqbpqvtdvhtdopoiqc2734hqjpb5tbrxv76q2hug52wjlw4)
274 +
[keymap.json](/blog-images/other/snip/bafkreieaxwbdqbpqvtdvhtdopoiqc2734hqjpb5tbrxv76q2hug52wjlw4)
src/content/post/resizing-ipfs-images.mdx +2 −2
3 3
publishDate: "23 June 2022"
4 4
description: "Learn how to use Pinata's Dedicated Gateway image optimization tools"
5 5
tags: ["web3", "ipfs", "tutorials", "web development"]
6 -
ogImage: "https://miro.medium.com/v2/resize:fit:4800/format:webp/1*Tp42Ey9Uvdb6njsaXHBOTA.jpeg"
6 +
ogImage: "/blog-images/medium/v2/resize:fit:4800/format:webp/1*Tp42Ey9Uvdb6njsaXHBOTA.jpeg"
7 7
---
8 8
9 9
import medium from "../../assets/medium.png";
191 191
export default App;
192 192
```
193 193
194 -
![gif of loading](https://miro.medium.com/v2/resize:fit:1400/1*E8ZdVathmGSrjQn6lz0VYw.gif)
194 +
![gif of loading](/blog-images/medium/v2/resize:fit:1400/1*E8ZdVathmGSrjQn6lz0VYw.gif)
195 195
196 196
Keep in mind, each one of these images is 10,000 x 10,000 resolution with over 35MB per file, and thanks to our dedicated gateway we loaded them like it was nothing. All of them dynamically resized to 1080 x 1080, still a decent size and high enough quality for most projects.
197 197
src/content/post/the-meaning-of-life.mdx +2 −2
3 3
publishDate: "15 Aug 2025"
4 4
description: "42? Sorta, but not exactly"
5 5
tags: ["personal", "philosophy"]
6 -
ogImage: "https://files.stevedylan.dev/42.png"
6 +
ogImage: "/blog-images/files-stevedylan-dev/42.png"
7 7
---
8 8
9 -
![cover](https://files.stevedylan.dev/42.png)
9 +
![cover](/blog-images/files-stevedylan-dev/42.png)
10 10
11 11
Even if you haven't read the book or seen the movie, you are likely familiar with the famous yet humorous answer to the meaning of life: 42. Just a number. The original author didn't intend any deeper meaning and stresses that it means nothing. Ironically I think like everything in life, it holds an ounce of truth beyond what we see in it.
12 12
src/content/post/the-power-of-dedicated-gateways.mdx +1 −1
3 3
publishDate: "10 Feb 2022"
4 4
description: "Dedicated Gateways. What they are, why they're essential, and how they can revolutionize a creator's next project."
5 5
tags: ["web3", "ipfs", "nfts", "tech philosophy"]
6 -
ogImage: "https://global-uploads.webflow.com/629e4fe96456f8219203e7f1/6410b46677b05b001afa5ff4_2022-02-10_The-Power-of_blog-img-tiny.png"
6 +
ogImage: "/blog-images/other/6410b46677b05b001afa5ff4_2022-02-10_The-Power-of_blog-img-tiny.png"
7 7
---
8 8
9 9
import pinnie from "../../assets/pinnie.png";
src/content/post/turning-natspec-into-markdown-ui.mdx +33 −30
3 3
publishDate: "31 Aug 2025"
4 4
description: "An exploration on how NatSpec could be used to not only maintain context but provide user interfaces"
5 5
tags: ["programming", "solidity", "smart contracts"]
6 -
ogImage: "https://files.stevedylan.dev/natspec-contract.png"
6 +
ogImage: "/blog-images/files-stevedylan-dev/natspec-contract.png"
7 7
---
8 8
9 -
![cover](https://files.stevedylan.dev/natspec-contract.png)
9 +
![cover](/blog-images/files-stevedylan-dev/natspec-contract.png)
10 10
11 11
One of the most common problems encountered when building decentralized applications is the disconnect between the smart contract and the client. A normal flow might look something like this:
12 12
32 32
}
33 33
```
34 34
35 -
All we know about this function is it probably sets a new number, but why? What is the number for? We might be able to answer these questions by looking at the source code of the contract itself, but there are many cases where we have no idea what a paramter is used for. This is something that [Seb brought up](https://farcaster.xyz/seb/0xf5694e6e) on Farcaster this weekend, stating "There should be some sort of universal markup language for every smart contract (that isn't an ABI) that allows anyone to easily interact onchain." 
35 +
All we know about this function is it probably sets a new number, but why? What is the number for? We might be able to answer these questions by looking at the source code of the contract itself, but there are many cases where we have no idea what a paramter is used for. This is something that [Seb brought up](https://farcaster.xyz/seb/0xf5694e6e) on Farcaster this weekend, stating "There should be some sort of universal markup language for every smart contract (that isn't an ABI) that allows anyone to easily interact onchain."
36 36
37 37
This got me wondering if the NatSpec could be used to help solve this problem. If you're not familiar with it, the [NatSpec](https://docs.soliditylang.org/en/latest/natspec-format.html) works a lot like JSDoc where the developer can leave comments in a particular format that can be used by the compiler to create documentation or even SDKs and CLIs. It's been in Solidity for years and has actually been used by OpenZeppelin's documentation to generate API references. While most of the tags handle things like parameters or returns, the `@notice` tag can be used as a general description and be filled with whatever we want to write, so why not markdown? It doesn't stop there though. What if we could build entire UIs out of the NatSpec? Thanks to a new library / proposed standard called [Markdown UI](https://markdown-ui.com/) I was able to build a [MVP](https://natspec-ui.orbiter.website) of this idea, and in this post I'll show you how it works!
38 38
39 39
The first thing you need to do is write up the markdown as NatSpec in the smart contract.
40 40
41 -
```solidity
41 +
````solidity
42 42
// SPDX-License-Identifier: MIT
43 43
pragma solidity ^0.8.20;
44 44
53 53
    /// @notice Sets the counter to a specific value
54 54
    /// @dev Updates the number state variable to the provided value
55 55
    /// @param newNumber The new value to set the counter to \n
56 -
    /// \n 
56 +
    /// \n
57 57
    /// ```markdown-ui-widget \n
58 58
    /// { "type": "form", "id": "setNumber", "submitLabel": "Set Number", "fields": [{ "type": "text-input", "id": "newValue", "label": "New Counter Value", "placeholder": "Enter number", "default": "42" }] } \n
59 59
    /// ``` \n
71 71
        number++;
72 72
    }
73 73
}
74 -
```
74 +
````
75 75
76 -
You might have noticed our one small twist: the Markdown UI component. 
76 +
You might have noticed our one small twist: the Markdown UI component.
77 77
78 78
```markdown
79 -
80 -
``markdown-ui-widget
79 +
`markdown-ui-widget
81 80
{ "type": "form", "id": "increment", "submitLabel": "Increment", "fields": [] }
82 -
``
83 -
81 +
`
84 82
```
85 83
86 84
This is what we can use in our front end to build interactive components along side the markdown describing how it works! When we compile this contract it's going to include a json file with out generated `userdoc` and `devdoc`. In order to make it easier to share these files along with the ABI, we can verify the contract with [Sourcify](https://sourcify.dev/) which will store our contract metadata for anyone to fetch via an API. That API response looks something like this when we use the query `?fields=devdoc`:
87 85
88 -
```json
86 +
````json
89 87
{
90 88
  "devdoc": {
91 89
    "kind": "dev",
117 115
  "chainId": "11155111",
118 116
  "address": "0xEeF9B4a84C3327860CD14E1E066D7D6762b9bC3F"
119 117
}
120 -
```
118 +
````
121 119
122 120
As you can see we're able to get all of the markdown we put in earlier. Now all we have to do is create a frontend client that can render it all!
123 121
124 122
```typescript
125 123
import { useState, useEffect } from "react";
126 -
import { MarkdownUI } from '@markdown-ui/react';
127 -
import { Marked } from 'marked';
128 -
import { markedUiExtension } from '@markdown-ui/marked-ext';
129 -
import '@markdown-ui/react/widgets.css';
130 -
import { parseContractToMarkdown, type ContractResponse } from './utils/contractParser';
124 +
import { MarkdownUI } from "@markdown-ui/react";
125 +
import { Marked } from "marked";
126 +
import { markedUiExtension } from "@markdown-ui/marked-ext";
127 +
import "@markdown-ui/react/widgets.css";
128 +
import {
129 +
  parseContractToMarkdown,
130 +
  type ContractResponse,
131 +
} from "./utils/contractParser";
131 132
132 133
const marked = new Marked().use(markedUiExtension);
133 134
146 147
        const response = await fetch(
147 148
          `https://sourcify.dev/server/v2/contract/${CHAIN_ID}/${CONTRACT_ADDRESS}?fields=devdoc`
148 149
        );
149 -
        
150 +
150 151
        if (!response.ok) {
151 152
          throw new Error(`HTTP error! status: ${response.status}`);
152 153
        }
153 -
        
154 +
154 155
        const data: ContractResponse = await response.json();
155 -
        
156 +
156 157
        const markdownContent = parseContractToMarkdown(data);
157 -
        
158 +
158 159
        console.log(markdownContent);
159 -
        const html = await marked.parse(markdownContent || '# No markdown widgets found');
160 +
        const html = await marked.parse(
161 +
          markdownContent || "# No markdown widgets found"
162 +
        );
160 163
        setContractHtml(html);
161 164
      } catch (err) {
162 -
        console.error('Error fetching contract data:', err);
163 -
        setError(err instanceof Error ? err.message : 'Unknown error');
165 +
        console.error("Error fetching contract data:", err);
166 +
        setError(err instanceof Error ? err.message : "Unknown error");
164 167
      } finally {
165 168
        setLoading(false);
166 169
      }
171 174
172 175
  if (loading) {
173 176
    return (
174 -
      <div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
177 +
      <div className="mx-auto flex min-h-screen max-w-xl flex-col items-center justify-center gap-6">
175 178
        <p>Loading contract data...</p>
176 179
      </div>
177 180
    );
179 182
180 183
  if (error) {
181 184
    return (
182 -
      <div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
185 +
      <div className="mx-auto flex min-h-screen max-w-xl flex-col items-center justify-center gap-6">
183 186
        <p className="text-red-500">Error: {error}</p>
184 187
      </div>
185 188
    );
186 189
  }
187 190
188 191
  return (
189 -
    <div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
192 +
    <div className="mx-auto flex min-h-screen max-w-xl flex-col items-center justify-center gap-6">
190 193
      <MarkdownUI html={contractHtml} />
191 194
    </div>
192 195
  );
195 198
export default App;
196 199
```
197 200
198 -
As a result we get a nice page that not only has markdown formatting but interactive UI components that are built in thanks to Markdown UI. 
201 +
As a result we get a nice page that not only has markdown formatting but interactive UI components that are built in thanks to Markdown UI.
199 202
200 -
![natspec demo](https://files.stevedylan.dev/natspec-markdown-ui-2.png)
203 +
![natspec demo](/blog-images/files-stevedylan-dev/natspec-markdown-ui-2.png)
201 204
202 205
I took a little extra time to add in Wagmi to the app which resulted in a fully interactive contract, which you can check out [here](https://natspec-ui.orbiter.website). In a sense we achievied the goal of a unified standard markup that can make user interactions with contracts easier. Of course we have to keep in mind the limitations here, primarily being it would require developers to make sure they include all of this markup in their contract and the Markdown UI standard isn't even out of a beta stage, and for that reason I would highly recommend a professional solution like the Contracts UI Builder. Nevertheless it's fun to see how extensible and open Markdown and Solidity have come in the past few years. Each day we're getting closer to an internet that is not only safe, but user friendly as well.
203 206
src/content/post/vibe-coding-and-kodak-cameras.mdx +6 −6
3 3
publishDate: "30 Mar 2025"
4 4
description: "A perspective on the rise of AI coding and how it relates to technological shifts throughout history"
5 5
tags: ["programming", "ai", "photography"]
6 -
ogImage: "https://files.stevedylan.dev/bafkreibzmkrvumddklcixiwq64qru7cv24na4uy2mvruk235lyrbkqjnbi.jpg"
6 +
ogImage: "/blog-images/files-stevedylan-dev/bafkreibzmkrvumddklcixiwq64qru7cv24na4uy2mvruk235lyrbkqjnbi.jpg"
7 7
---
8 8
9 -
![cover](https://files.stevedylan.dev/bafkreidwjks56b7ifrnltsepgknkygxukctjdywujcbh5c3uqu44lkefrq.jpg)
9 +
![cover](/blog-images/files-stevedylan-dev/bafkreidwjks56b7ifrnltsepgknkygxukctjdywujcbh5c3uqu44lkefrq.jpg)
10 10
11 11
I'm sure many who read this are familiar by now with the term "vibe coding," a euphoric style of programming where you prompt AI models or IDEs to write software and just "vibe." One of the more popular instances that made the practice takeoff was the indie hacker Levelsio building a flight simulator entirely in JavaScript and selling ad space within the game. Others have followed suit and even started businesses by vibe coding them into existence. It's hard to deny the reality that AI has been changing much of the software ecosystem, and with any major shift in technology there are lots of opinions. I certainly have my own, but I thought it would be more productive to look at history repeat itself.
12 12
14 14
15 15
Most people don't know it, but there was a major controversy in the world of photography in the year 1900. Before that time photography was an art form protected by its sages, who poured their money, time, and practice into it. Not anyone could just take a picture, only those who had worked in the craft and mastered it. All of that changed in February of 1900 when Kodak released the Brownie camera. It wasn't much to look at, a little cardboard box that could take a picture no bigger than 2.25 inches using 117mm film. What made it special was the service behind it. The Brownie only cost $1 (which would be $38 at the time of this post), and it included the cost to develop the film. All someone had to do was take a picture, send it off to Kodak, and they would return the picture. "You press the button—we do the rest."
16 16
17 -
![bronwie ad](https://files.stevedylan.dev/bafybeidg6ljbwwlfs3cvrekxwfyhw73pm577irs2h4zzkj4srm7q5x6dne.jpg)
17 +
![bronwie ad](/blog-images/files-stevedylan-dev/bafybeidg6ljbwwlfs3cvrekxwfyhw73pm577irs2h4zzkj4srm7q5x6dne.jpg)
18 18
19 19
Suddenly anyone could take photos: parents, grandparents, kids, truly anyone. It sold like crazy, and it upset the old photographer guild. There was great concern that there would now be a huge amount of "slop" photography and the art of photography would be washed away. Surely no one who took photos in such a way could be an artist... right? These artists were also concerned for their profession. If everyone had a camera, why would families pay for a photography session?
20 20
32 32
33 33
The evolution of photography technology enabled plenty of bad photos and photographers, but it also created a whole new series of artists we would not have otherwise. One of my favorite examples of this is Vivian Maier. If you're not familiar, Vivian Maier was an unknown nanny in the 1940's and 50's. It wasn't until after her death that her life's work as a photographer was discovered by a man who won it at an auction. To his surprise it was a stunning collection, hundreds of thousands of them, all taken by a nanny no one had ever heard of. She was passionate about photography, and her perspectives of the world at that time were unique.
34 34
35 -
![vivian maier](https://files.stevedylan.dev/bafybeicc62frhqspkismnm7ocuhvzutlffnxkgjuxirs46faig47btgwjm.jpg)
35 +
![vivian maier](/blog-images/files-stevedylan-dev/bafybeicc62frhqspkismnm7ocuhvzutlffnxkgjuxirs46faig47btgwjm.jpg)
36 36
37 37
They're made possible thanks to the much later successor of a Brownie style camera that she could take everywhere and shoot roll after roll of film. If photography was still stuck in the dark ages of carrying around big pieces of equipment that only certain people could afford, we wouldn't have the stunning work of Vivian Maier.
38 38
39 -
![photo by vivian maier](https://files.stevedylan.dev/bafkreicb3fn44kn2lfitq7bmoionmmw5maho3fvk2i7zr5x4xfafieafmu.jpg)
39 +
![photo by vivian maier](/blog-images/files-stevedylan-dev/bafkreicb3fn44kn2lfitq7bmoionmmw5maho3fvk2i7zr5x4xfafieafmu.jpg)
40 40
41 41
Another unrecognized photographer is one of my favorites, Joe Greer. Greer started his career on an app: Instagram. He didn't take photography lessons, he didn't have a nice camera, he just had his phone. The more and more he shot with his phone and posted his photos on Instagram, the more people liked them and the bigger it got. Eventually he did switch to professional cameras and continued his craft, but the key was his access to an art form that otherwise wouldn't be available apart from cell phone cameras.
42 42
43 -
![joe greer photo](https://files.stevedylan.dev/bafybeieyixzansv22m6kzhuqbi4vxqb5zlgdcwvpm7ikkzz5j3dugtdhtu.jpg)
43 +
![joe greer photo](/blog-images/files-stevedylan-dev/bafybeieyixzansv22m6kzhuqbi4vxqb5zlgdcwvpm7ikkzz5j3dugtdhtu.jpg)
44 44
45 45
In the realm of programming, even in the evolution of languages, we see a similar pattern where mediocrity increases but so does the number of discovered programmers. Sure there's a lot of Javascript slop out there, but thanks to Javascript there have been more and more people discovering programming and starting a wonderful journey. You don't have to start in a lower language to find the joy of programming, and most people who do find it will experiment in many different languages.
46 46
src/content/post/why-i-learned-vim.mdx +2 −10
3 3
publishDate: "05 Jan 2024"
4 4
description: "A brief look at my history and how ordinary jobs lead to learning programming and Vim/Neovim"
5 5
tags: ["programming", "vim", "neovim"]
6 -
ogImage: "https://res.cloudinary.com/df9dofjus/image/upload/v1704512309/Screenshot-Alacritty-01-05-2024-22-16_2x_hdiy9a.png"
6 +
ogImage: "/blog-images/cloudinary/v1704512309/Screenshot-Alacritty-01-05-2024-22-16_2x_hdiy9a.png"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 -
11 -
<Image
12 -
	src="https://res.cloudinary.com/df9dofjus/image/upload/v1704512309/Screenshot-Alacritty-01-05-2024-22-16_2x_hdiy9a.png"
13 -
	alt="header image"
14 -
	height={600}
15 -
	width={800}
16 -
	aspectRatio={3 / 2}
17 -
/>
9 +
![image](/blog-images/placeholder.png)>
18 10
19 11
Something I see pretty consistently when the topic of Vim/Neovim comes up is the inevitable question "Why?" Thanks to years of memes about not being able to quit Vim, it has gained a cult popularity of being "elite," "ancient," or both. Most developers who are comfortable in their VSCode environment can't imagine using something so unfriendly and perhaps ugly. So yeah, "why" is a fair question. I reflected on that question and I realized how my own life experience led me to learn Vim, and perhaps it would be helpful to share that story.
20 12
src/content/post/why-you-should-learn-jq-in-2024.mdx +3 −12
3 3
publishDate: "12 Oct 2024"
4 4
description: "Discover why learning jq isn't just about boosting your productivity, it's about becoming a more curious developer"
5 5
tags: ["programming", "developer tools", "jq"]
6 -
ogImage: "https://files.stevedylan.dev/bafybeihderpsuxl43msvzletfuhuqw75ygo3jhbh2psiboate4xc7gzhde.webp"
6 +
ogImage: "/blog-images/files-stevedylan-dev/bafybeihderpsuxl43msvzletfuhuqw75ygo3jhbh2psiboate4xc7gzhde.webp"
7 7
---
8 8
9 -
import { Image } from "astro:assets";
10 -
11 -
<Image
12 -
	src="https://files.stevedylan.dev/bafybeihderpsuxl43msvzletfuhuqw75ygo3jhbh2psiboate4xc7gzhde.webp"
13 -
	alt="header image"
14 -
	height={1080}
15 -
	width={1920}
16 -
	aspectRatio={9 / 16}
17 -
/>
18 -
9 +
![header image](/blog-images/files-stevedylan-dev/bafybeihderpsuxl43msvzletfuhuqw75ygo3jhbh2psiboate4xc7gzhde.webp)
19 10
20 11
The chances are that if you are a modern developer or if you're starting out, you probably don't know what `jq` is, and that's why I'm writing this post. It won't take long to explain what `jq` is, so let's just get that out of the way.
21 12
155 146
156 147
Not all abstraction is bad. For example, I would much rather use [Pinata](https://pinata.cloud) than S3, but I won’t deny that it could be good to learn how to use S3. In today’s developer ecosystem it’s becoming a growing skill to learn what is worth abstracting and what isn’t. For example, I personally don’t find much value in abstracting `git` into a formal gui or dedicated client, however I do find value in using a tui like [lazygit](https://github.com/jesseduffield/lazygit).
157 148
158 -
Like most things in life, there are so many gray areas in our current developer environment. Most of this comes down to preference, and it could be argued from one side to the other. Sometimes it’s more important to ship a product, sometimes it’s more important to really understand what you’re doing. Ultimately it's up to you how far you want to go down the rabbit hole, or perhaps even more important, how *open* you are to the possibilities.
149 +
Like most things in life, there are so many gray areas in our current developer environment. Most of this comes down to preference, and it could be argued from one side to the other. Sometimes it’s more important to ship a product, sometimes it’s more important to really understand what you’re doing. Ultimately it's up to you how far you want to go down the rabbit hole, or perhaps even more important, how _open_ you are to the possibilities.
159 150
160 151
In my opinion the best developer is a curious one. "How does this work? Why does this work? How could I use this elsewhere?" Obviously you can't learn everything, and you don't necessarily need to these days, but curiosity grows your knowledge. I don't have to write in basic at all for my job, but that hasn't kept me from at least being curious and exploring how it works. The magic of programming is the ability to control computers and data. As was said in a recent stream with DHH and The Primeagen, "It's more fun to be competent," and I couldn't agree more.
161 152