diff --git a/fs.go b/fs.go
index 796c821..1d8d048 100644
--- a/fs.go
+++ b/fs.go
@@ -33,6 +33,11 @@ var fsCmd = &cli.Command{
 				return fmt.Errorf("invalid public key '%s'", pk)
 			},
 		},
+		&cli.DurationFlag{
+			Name:  "auto-publish",
+			Usage: "delay after which edited articles will be auto-published.",
+			Value: time.Hour * 24 * 365 * 2,
+		},
 	),
 	DisableSliceFlagSeparator: true,
 	Action: func(ctx context.Context, c *cli.Command) error {
@@ -49,10 +54,19 @@ var fsCmd = &cli.Command{
 		}
 
 		root := nostrfs.NewNostrRoot(
-			context.WithValue(ctx, "log", log),
+			context.WithValue(
+				context.WithValue(
+					ctx,
+					"log", log,
+				),
+				"logverbose", logverbose,
+			),
 			sys,
 			kr,
 			mountpoint,
+			nostrfs.Options{
+				AutoPublishTimeout: c.Duration("auto-publish"),
+			},
 		)
 
 		// create the server
@@ -60,9 +74,10 @@ var fsCmd = &cli.Command{
 		timeout := time.Second * 120
 		server, err := fs.Mount(mountpoint, root, &fs.Options{
 			MountOptions: fuse.MountOptions{
-				Debug:  isVerbose,
-				Name:   "nak",
-				FsName: "nak",
+				Debug:          isVerbose,
+				Name:           "nak",
+				FsName:         "nak",
+				RememberInodes: true,
 			},
 			AttrTimeout:  &timeout,
 			EntryTimeout: &timeout,
@@ -71,7 +86,7 @@ var fsCmd = &cli.Command{
 		if err != nil {
 			return fmt.Errorf("mount failed: %w", err)
 		}
-		log("ok\n")
+		log("ok.\n")
 
 		// setup signal handling for clean unmount
 		ch := make(chan os.Signal, 1)
diff --git a/nostrfs/entitydir.go b/nostrfs/entitydir.go
index 9a242aa..bfdd275 100644
--- a/nostrfs/entitydir.go
+++ b/nostrfs/entitydir.go
@@ -30,30 +30,29 @@ type EntityDir struct {
 	root *NostrRoot
 
 	publisher *debouncer.Debouncer
-	extension string
 	event     *nostr.Event
 	updating  struct {
-		title   string
-		content string
+		title       string
+		content     string
+		publishedAt uint64
 	}
 }
 
 var (
 	_ = (fs.NodeOnAdder)((*EntityDir)(nil))
 	_ = (fs.NodeGetattrer)((*EntityDir)(nil))
+	_ = (fs.NodeSetattrer)((*EntityDir)(nil))
 	_ = (fs.NodeCreater)((*EntityDir)(nil))
 	_ = (fs.NodeUnlinker)((*EntityDir)(nil))
 )
 
 func (e *EntityDir) Getattr(_ context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
-	publishedAt := uint64(e.event.CreatedAt)
-	out.Ctime = publishedAt
-
-	if tag := e.event.Tags.Find("published_at"); tag != nil {
-		publishedAt, _ = strconv.ParseUint(tag[1], 10, 64)
+	out.Ctime = uint64(e.event.CreatedAt)
+	if e.updating.publishedAt != 0 {
+		out.Mtime = e.updating.publishedAt
+	} else {
+		out.Mtime = e.PublishedAt()
 	}
-	out.Mtime = publishedAt
-
 	return fs.OK
 }
 
@@ -64,8 +63,10 @@ func (e *EntityDir) Create(
 	mode uint32,
 	out *fuse.EntryOut,
 ) (node *fs.Inode, fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
-	if name == "publish" {
+	if name == "publish" && e.publisher.IsRunning() {
 		// this causes the publish process to be triggered faster
+		log := e.root.ctx.Value("log").(func(msg string, args ...any))
+		log("publishing now!\n")
 		e.publisher.Flush()
 		return nil, nil, 0, syscall.ENOTDIR
 	}
@@ -75,26 +76,24 @@ func (e *EntityDir) Create(
 
 func (e *EntityDir) Unlink(ctx context.Context, name string) syscall.Errno {
 	switch name {
-	case "content" + e.extension:
+	case "content" + kindToExtension(e.event.Kind):
 		e.updating.content = e.event.Content
 		return syscall.ENOTDIR
 	case "title":
-		e.updating.title = ""
-		if titleTag := e.event.Tags.Find("title"); titleTag != nil {
-			e.updating.title = titleTag[1]
-		}
+		e.updating.title = e.Title()
 		return syscall.ENOTDIR
 	default:
 		return syscall.EINTR
 	}
 }
 
+func (e *EntityDir) Setattr(_ context.Context, _ fs.FileHandle, in *fuse.SetAttrIn, _ *fuse.AttrOut) syscall.Errno {
+	e.updating.publishedAt = in.Mtime
+	return fs.OK
+}
+
 func (e *EntityDir) OnAdd(_ context.Context) {
 	log := e.root.ctx.Value("log").(func(msg string, args ...any))
-	publishedAt := uint64(e.event.CreatedAt)
-	if tag := e.event.Tags.Find("published_at"); tag != nil {
-		publishedAt, _ = strconv.ParseUint(tag[1], 10, 64)
-	}
 
 	npub, _ := nip19.EncodePublicKey(e.event.PubKey)
 	e.AddChild("@author", e.NewPersistentInode(
@@ -132,42 +131,35 @@ func (e *EntityDir) OnAdd(_ context.Context) {
 		fs.StableAttr{},
 	), true)
 
-	if e.root.signer == nil {
+	if e.root.signer == nil || e.root.rootPubKey != e.event.PubKey {
 		// read-only
 		e.AddChild("title", e.NewPersistentInode(
 			e.root.ctx,
 			&DeterministicFile{
 				get: func() (ctime uint64, mtime uint64, data string) {
-					var title string
-					if tag := e.event.Tags.Find("title"); tag != nil {
-						title = tag[1]
-					} else {
-						title = e.event.Tags.GetD()
-					}
-					return uint64(e.event.CreatedAt), publishedAt, title
+					return uint64(e.event.CreatedAt), e.PublishedAt(), e.Title()
 				},
 			},
 			fs.StableAttr{},
 		), true)
-		e.AddChild("content."+e.extension, e.NewPersistentInode(
+		e.AddChild("content."+kindToExtension(e.event.Kind), e.NewPersistentInode(
 			e.root.ctx,
 			&DeterministicFile{
 				get: func() (ctime uint64, mtime uint64, data string) {
-					return uint64(e.event.CreatedAt), publishedAt, e.event.Content
+					return uint64(e.event.CreatedAt), e.PublishedAt(), e.event.Content
 				},
 			},
 			fs.StableAttr{},
 		), true)
 	} else {
 		// writeable
-		if tag := e.event.Tags.Find("title"); tag != nil {
-			e.updating.title = tag[1]
-		}
+		e.updating.title = e.Title()
+		e.updating.publishedAt = e.PublishedAt()
 		e.updating.content = e.event.Content
 
 		e.AddChild("title", e.NewPersistentInode(
 			e.root.ctx,
-			e.root.NewWriteableFile(e.updating.title, uint64(e.event.CreatedAt), publishedAt, func(s string) {
+			e.root.NewWriteableFile(e.updating.title, uint64(e.event.CreatedAt), e.updating.publishedAt, func(s string) {
 				log("title updated")
 				e.updating.title = strings.TrimSpace(s)
 				e.handleWrite()
@@ -175,9 +167,9 @@ func (e *EntityDir) OnAdd(_ context.Context) {
 			fs.StableAttr{},
 		), true)
 
-		e.AddChild("content."+e.extension, e.NewPersistentInode(
+		e.AddChild("content."+kindToExtension(e.event.Kind), e.NewPersistentInode(
 			e.root.ctx,
-			e.root.NewWriteableFile(e.updating.content, uint64(e.event.CreatedAt), publishedAt, func(s string) {
+			e.root.NewWriteableFile(e.updating.content, uint64(e.event.CreatedAt), e.updating.publishedAt, func(s string) {
 				log("content updated")
 				e.updating.content = strings.TrimSpace(s)
 				e.handleWrite()
@@ -254,21 +246,51 @@ func (e *EntityDir) OnAdd(_ context.Context) {
 	}
 }
 
+func (e *EntityDir) IsNew() bool {
+	return e.event.CreatedAt == 0
+}
+
+func (e *EntityDir) PublishedAt() uint64 {
+	if tag := e.event.Tags.Find("published_at"); tag != nil {
+		publishedAt, _ := strconv.ParseUint(tag[1], 10, 64)
+		return publishedAt
+	}
+	return uint64(e.event.CreatedAt)
+}
+
+func (e *EntityDir) Title() string {
+	if tag := e.event.Tags.Find("title"); tag != nil {
+		return tag[1]
+	}
+	return ""
+}
+
 func (e *EntityDir) handleWrite() {
 	log := e.root.ctx.Value("log").(func(msg string, args ...any))
+	logverbose := e.root.ctx.Value("logverbose").(func(msg string, args ...any))
 
-	if e.publisher.IsRunning() {
-		log(", timer reset")
+	if e.root.opts.AutoPublishTimeout.Hours() < 24*365 {
+		if e.publisher.IsRunning() {
+			log(", timer reset")
+		}
+		log(", will publish the ")
+		if e.IsNew() {
+			log("new")
+		} else {
+			log("updated")
+		}
+		log(" event in %d seconds...\n", e.root.opts.AutoPublishTimeout.Seconds())
+	} else {
+		log(".\n")
 	}
-	log(", will publish the updated event in 30 seconds...\n")
 	if !e.publisher.IsRunning() {
 		log("- `touch publish` to publish immediately\n")
-		log("- `rm title content." + e.extension + "` to erase and cancel the edits\n")
+		log("- `rm title content." + kindToExtension(e.event.Kind) + "` to erase and cancel the edits\n")
 	}
 
 	e.publisher.Call(func() {
-		if currentTitle := e.event.Tags.Find("title"); (currentTitle != nil && currentTitle[1] == e.updating.title) || (currentTitle == nil && e.updating.title == "") && e.updating.content == e.event.Content {
-			log("back into the previous state, not publishing.\n")
+		if e.Title() == e.updating.title && e.event.Content == e.updating.content {
+			log("not modified, publish canceled.\n")
 			return
 		}
 
@@ -278,7 +300,7 @@ func (e *EntityDir) handleWrite() {
 			Tags:      make(nostr.Tags, len(e.event.Tags)),
 			CreatedAt: nostr.Now(),
 		}
-		copy(evt.Tags, e.event.Tags)
+		copy(evt.Tags, e.event.Tags) // copy tags because that's the rule
 		if e.updating.title != "" {
 			if titleTag := evt.Tags.Find("title"); titleTag != nil {
 				titleTag[1] = e.updating.title
@@ -286,24 +308,42 @@ func (e *EntityDir) handleWrite() {
 				evt.Tags = append(evt.Tags, nostr.Tag{"title", e.updating.title})
 			}
 		}
-		if publishedAtTag := evt.Tags.Find("published_at"); publishedAtTag == nil {
-			evt.Tags = append(evt.Tags, nostr.Tag{
-				"published_at",
-				strconv.FormatInt(int64(e.event.CreatedAt), 10),
-			})
+
+		// "published_at" tag
+		publishedAtStr := strconv.FormatUint(e.updating.publishedAt, 10)
+		if publishedAtStr != "0" {
+			if publishedAtTag := evt.Tags.Find("published_at"); publishedAtTag != nil {
+				publishedAtTag[1] = publishedAtStr
+			} else {
+				evt.Tags = append(evt.Tags, nostr.Tag{"published_at", publishedAtStr})
+			}
 		}
+
+		// add "p" tags from people mentioned and "q" tags from events mentioned
 		for ref := range nip27.ParseReferences(evt) {
 			tag := ref.Pointer.AsTag()
-			if existing := evt.Tags.FindWithValue(tag[0], tag[1]); existing == nil {
+			key := tag[0]
+			val := tag[1]
+			if key == "e" || key == "a" {
+				key = "q"
+			}
+			if existing := evt.Tags.FindWithValue(key, val); existing == nil {
 				evt.Tags = append(evt.Tags, tag)
 			}
 		}
+
+		// sign and publish
 		if err := e.root.signer.SignEvent(e.root.ctx, &evt); err != nil {
 			log("failed to sign: '%s'.\n", err)
 			return
 		}
+		logverbose("%s\n", evt)
 
 		relays := e.root.sys.FetchWriteRelays(e.root.ctx, evt.PubKey, 8)
+		if len(relays) == 0 {
+			relays = e.root.sys.FetchOutboxRelays(e.root.ctx, evt.PubKey, 6)
+		}
+
 		log("publishing to %d relays... ", len(relays))
 		success := false
 		first := true
@@ -330,6 +370,7 @@ func (e *EntityDir) handleWrite() {
 		if success {
 			e.event = &evt
 			log("event updated locally.\n")
+			e.updating.publishedAt = uint64(evt.CreatedAt) // set this so subsequent edits get the correct value
 		} else {
 			log("failed.\n")
 		}
@@ -348,17 +389,16 @@ func (r *NostrRoot) FetchAndCreateEntityDir(
 		return nil, fmt.Errorf("failed to fetch: %w", err)
 	}
 
-	return r.CreateEntityDir(parent, extension, event), nil
+	return r.CreateEntityDir(parent, event), nil
 }
 
 func (r *NostrRoot) CreateEntityDir(
 	parent fs.InodeEmbedder,
-	extension string,
 	event *nostr.Event,
 ) *fs.Inode {
 	return parent.EmbeddedInode().NewPersistentInode(
 		r.ctx,
-		&EntityDir{root: r, event: event, publisher: debouncer.New(time.Second * 30), extension: extension},
-		fs.StableAttr{Mode: syscall.S_IFDIR, Ino: hexToUint64(event.ID)},
+		&EntityDir{root: r, event: event, publisher: debouncer.New(r.opts.AutoPublishTimeout)},
+		fs.StableAttr{Mode: syscall.S_IFDIR},
 	)
 }
diff --git a/nostrfs/helpers.go b/nostrfs/helpers.go
index 9cd82e5..e067e6b 100644
--- a/nostrfs/helpers.go
+++ b/nostrfs/helpers.go
@@ -2,6 +2,17 @@ package nostrfs
 
 import "strconv"
 
+func kindToExtension(kind int) string {
+	switch kind {
+	case 30023:
+		return "md"
+	case 30818:
+		return "adoc"
+	default:
+		return "txt"
+	}
+}
+
 func hexToUint64(hexStr string) uint64 {
 	v, _ := strconv.ParseUint(hexStr[16:32], 16, 64)
 	return v
diff --git a/nostrfs/npubdir.go b/nostrfs/npubdir.go
index 633137e..b721002 100644
--- a/nostrfs/npubdir.go
+++ b/nostrfs/npubdir.go
@@ -10,10 +10,12 @@ import (
 	"syscall"
 	"time"
 
+	"github.com/fatih/color"
 	"github.com/hanwen/go-fuse/v2/fs"
 	"github.com/hanwen/go-fuse/v2/fuse"
 	"github.com/liamg/magic"
 	"github.com/nbd-wtf/go-nostr"
+	"github.com/nbd-wtf/go-nostr/nip19"
 )
 
 type NpubDir struct {
@@ -23,28 +25,36 @@ type NpubDir struct {
 	fetched atomic.Bool
 }
 
+var _ = (fs.NodeOnAdder)((*NpubDir)(nil))
+
 func (r *NostrRoot) CreateNpubDir(
 	parent fs.InodeEmbedder,
 	pointer nostr.ProfilePointer,
 	signer nostr.Signer,
 ) *fs.Inode {
 	npubdir := &NpubDir{root: r, pointer: pointer}
-	h := parent.EmbeddedInode().NewPersistentInode(
+	return parent.EmbeddedInode().NewPersistentInode(
 		r.ctx,
 		npubdir,
 		fs.StableAttr{Mode: syscall.S_IFDIR, Ino: hexToUint64(pointer.PublicKey)},
 	)
+}
 
-	relays := r.sys.FetchOutboxRelays(r.ctx, pointer.PublicKey, 2)
+func (h *NpubDir) OnAdd(_ context.Context) {
+	log := h.root.ctx.Value("log").(func(msg string, args ...any))
+
+	relays := h.root.sys.FetchOutboxRelays(h.root.ctx, h.pointer.PublicKey, 2)
+	log("- adding folder for %s with relays %s\n",
+		color.HiYellowString(nip19.EncodePointer(h.pointer)), color.HiGreenString("%v", relays))
 
 	h.AddChild("pubkey", h.NewPersistentInode(
-		r.ctx,
-		&fs.MemRegularFile{Data: []byte(pointer.PublicKey + "\n"), Attr: fuse.Attr{Mode: 0444}},
+		h.root.ctx,
+		&fs.MemRegularFile{Data: []byte(h.pointer.PublicKey + "\n"), Attr: fuse.Attr{Mode: 0444}},
 		fs.StableAttr{},
 	), true)
 
 	go func() {
-		pm := r.sys.FetchProfileMetadata(r.ctx, pointer.PublicKey)
+		pm := h.root.sys.FetchProfileMetadata(h.root.ctx, h.pointer.PublicKey)
 		if pm.Event == nil {
 			return
 		}
@@ -53,7 +63,7 @@ func (r *NostrRoot) CreateNpubDir(
 		h.AddChild(
 			"metadata.json",
 			h.NewPersistentInode(
-				r.ctx,
+				h.root.ctx,
 				&fs.MemRegularFile{
 					Data: metadataj,
 					Attr: fuse.Attr{
@@ -66,11 +76,11 @@ func (r *NostrRoot) CreateNpubDir(
 			true,
 		)
 
-		ctx, cancel := context.WithTimeout(r.ctx, time.Second*20)
+		ctx, cancel := context.WithTimeout(h.root.ctx, time.Second*20)
 		defer cancel()
-		r, err := http.NewRequestWithContext(ctx, "GET", pm.Picture, nil)
+		req, err := http.NewRequestWithContext(ctx, "GET", pm.Picture, nil)
 		if err == nil {
-			resp, err := http.DefaultClient.Do(r)
+			resp, err := http.DefaultClient.Do(req)
 			if err == nil {
 				defer resp.Body.Close()
 				if resp.StatusCode < 300 {
@@ -98,145 +108,152 @@ func (r *NostrRoot) CreateNpubDir(
 		}
 	}()
 
-	h.AddChild(
-		"notes",
-		h.NewPersistentInode(
-			r.ctx,
-			&ViewDir{
-				root: r,
-				filter: nostr.Filter{
-					Kinds:   []int{1},
-					Authors: []string{pointer.PublicKey},
+	if h.GetChild("notes") == nil {
+		h.AddChild(
+			"notes",
+			h.NewPersistentInode(
+				h.root.ctx,
+				&ViewDir{
+					root: h.root,
+					filter: nostr.Filter{
+						Kinds:   []int{1},
+						Authors: []string{h.pointer.PublicKey},
+					},
+					paginate:    true,
+					relays:      relays,
+					replaceable: false,
 				},
-				paginate:    true,
-				relays:      relays,
-				replaceable: false,
-				extension:   "txt",
-			},
-			fs.StableAttr{Mode: syscall.S_IFDIR},
-		),
-		true,
-	)
+				fs.StableAttr{Mode: syscall.S_IFDIR},
+			),
+			true,
+		)
+	}
 
-	h.AddChild(
-		"comments",
-		h.NewPersistentInode(
-			r.ctx,
-			&ViewDir{
-				root: r,
-				filter: nostr.Filter{
-					Kinds:   []int{1111},
-					Authors: []string{pointer.PublicKey},
+	if h.GetChild("comments") == nil {
+		h.AddChild(
+			"comments",
+			h.NewPersistentInode(
+				h.root.ctx,
+				&ViewDir{
+					root: h.root,
+					filter: nostr.Filter{
+						Kinds:   []int{1111},
+						Authors: []string{h.pointer.PublicKey},
+					},
+					paginate:    true,
+					relays:      relays,
+					replaceable: false,
 				},
-				paginate:    true,
-				relays:      relays,
-				replaceable: false,
-				extension:   "txt",
-			},
-			fs.StableAttr{Mode: syscall.S_IFDIR},
-		),
-		true,
-	)
+				fs.StableAttr{Mode: syscall.S_IFDIR},
+			),
+			true,
+		)
+	}
 
-	h.AddChild(
-		"photos",
-		h.NewPersistentInode(
-			r.ctx,
-			&ViewDir{
-				root: r,
-				filter: nostr.Filter{
-					Kinds:   []int{20},
-					Authors: []string{pointer.PublicKey},
+	if h.GetChild("photos") == nil {
+		h.AddChild(
+			"photos",
+			h.NewPersistentInode(
+				h.root.ctx,
+				&ViewDir{
+					root: h.root,
+					filter: nostr.Filter{
+						Kinds:   []int{20},
+						Authors: []string{h.pointer.PublicKey},
+					},
+					paginate:    true,
+					relays:      relays,
+					replaceable: false,
 				},
-				paginate:    true,
-				relays:      relays,
-				replaceable: false,
-				extension:   "txt",
-			},
-			fs.StableAttr{Mode: syscall.S_IFDIR},
-		),
-		true,
-	)
+				fs.StableAttr{Mode: syscall.S_IFDIR},
+			),
+			true,
+		)
+	}
 
-	h.AddChild(
-		"videos",
-		h.NewPersistentInode(
-			r.ctx,
-			&ViewDir{
-				root: r,
-				filter: nostr.Filter{
-					Kinds:   []int{21, 22},
-					Authors: []string{pointer.PublicKey},
+	if h.GetChild("videos") == nil {
+		h.AddChild(
+			"videos",
+			h.NewPersistentInode(
+				h.root.ctx,
+				&ViewDir{
+					root: h.root,
+					filter: nostr.Filter{
+						Kinds:   []int{21, 22},
+						Authors: []string{h.pointer.PublicKey},
+					},
+					paginate:    false,
+					relays:      relays,
+					replaceable: false,
 				},
-				paginate:    false,
-				relays:      relays,
-				replaceable: false,
-				extension:   "txt",
-			},
-			fs.StableAttr{Mode: syscall.S_IFDIR},
-		),
-		true,
-	)
+				fs.StableAttr{Mode: syscall.S_IFDIR},
+			),
+			true,
+		)
+	}
 
-	h.AddChild(
-		"highlights",
-		h.NewPersistentInode(
-			r.ctx,
-			&ViewDir{
-				root: r,
-				filter: nostr.Filter{
-					Kinds:   []int{9802},
-					Authors: []string{pointer.PublicKey},
+	if h.GetChild("highlights") == nil {
+		h.AddChild(
+			"highlights",
+			h.NewPersistentInode(
+				h.root.ctx,
+				&ViewDir{
+					root: h.root,
+					filter: nostr.Filter{
+						Kinds:   []int{9802},
+						Authors: []string{h.pointer.PublicKey},
+					},
+					paginate:    false,
+					relays:      relays,
+					replaceable: false,
 				},
-				paginate:    false,
-				relays:      relays,
-				replaceable: false,
-				extension:   "txt",
-			},
-			fs.StableAttr{Mode: syscall.S_IFDIR},
-		),
-		true,
-	)
+				fs.StableAttr{Mode: syscall.S_IFDIR},
+			),
+			true,
+		)
+	}
 
-	h.AddChild(
-		"articles",
-		h.NewPersistentInode(
-			r.ctx,
-			&ViewDir{
-				root: r,
-				filter: nostr.Filter{
-					Kinds:   []int{30023},
-					Authors: []string{pointer.PublicKey},
+	if h.GetChild("articles") == nil {
+		h.AddChild(
+			"articles",
+			h.NewPersistentInode(
+				h.root.ctx,
+				&ViewDir{
+					root: h.root,
+					filter: nostr.Filter{
+						Kinds:   []int{30023},
+						Authors: []string{h.pointer.PublicKey},
+					},
+					paginate:    false,
+					relays:      relays,
+					replaceable: true,
+					createable:  true,
 				},
-				paginate:    false,
-				relays:      relays,
-				replaceable: true,
-				extension:   "md",
-			},
-			fs.StableAttr{Mode: syscall.S_IFDIR},
-		),
-		true,
-	)
+				fs.StableAttr{Mode: syscall.S_IFDIR},
+			),
+			true,
+		)
+	}
 
-	h.AddChild(
-		"wiki",
-		h.NewPersistentInode(
-			r.ctx,
-			&ViewDir{
-				root: r,
-				filter: nostr.Filter{
-					Kinds:   []int{30818},
-					Authors: []string{pointer.PublicKey},
+	if h.GetChild("wiki") == nil {
+		h.AddChild(
+			"wiki",
+			h.NewPersistentInode(
+				h.root.ctx,
+				&ViewDir{
+					root: h.root,
+					filter: nostr.Filter{
+						Kinds:   []int{30818},
+						Authors: []string{h.pointer.PublicKey},
+					},
+					paginate:    false,
+					relays:      relays,
+					replaceable: true,
+					createable:  true,
 				},
-				paginate:    false,
-				relays:      relays,
-				replaceable: true,
-				extension:   "adoc",
-			},
-			fs.StableAttr{Mode: syscall.S_IFDIR},
-		),
-		true,
-	)
-
-	return h
+				fs.StableAttr{Mode: syscall.S_IFDIR},
+			),
+			true,
+		)
+	}
 }
diff --git a/nostrfs/root.go b/nostrfs/root.go
index c36ec1a..510969c 100644
--- a/nostrfs/root.go
+++ b/nostrfs/root.go
@@ -14,6 +14,10 @@ import (
 	"github.com/nbd-wtf/go-nostr/sdk"
 )
 
+type Options struct {
+	AutoPublishTimeout time.Duration // a negative number means do not publish
+}
+
 type NostrRoot struct {
 	fs.Inode
 
@@ -22,11 +26,13 @@ type NostrRoot struct {
 	sys        *sdk.System
 	rootPubKey string
 	signer     nostr.Signer
+
+	opts Options
 }
 
 var _ = (fs.NodeOnAdder)((*NostrRoot)(nil))
 
-func NewNostrRoot(ctx context.Context, sys *sdk.System, user nostr.User, mountpoint string) *NostrRoot {
+func NewNostrRoot(ctx context.Context, sys *sdk.System, user nostr.User, mountpoint string, o Options) *NostrRoot {
 	pubkey, _ := user.GetPublicKey(ctx)
 	abs, _ := filepath.Abs(mountpoint)
 
@@ -41,6 +47,8 @@ func NewNostrRoot(ctx context.Context, sys *sdk.System, user nostr.User, mountpo
 		rootPubKey: pubkey,
 		signer:     signer,
 		wd:         abs,
+
+		opts: o,
 	}
 }
 
@@ -49,36 +57,40 @@ func (r *NostrRoot) OnAdd(_ context.Context) {
 		return
 	}
 
-	// add our contacts
-	fl := r.sys.FetchFollowList(r.ctx, r.rootPubKey)
-	for _, f := range fl.Items {
-		pointer := nostr.ProfilePointer{PublicKey: f.Pubkey, Relays: []string{f.Relay}}
-		npub, _ := nip19.EncodePublicKey(f.Pubkey)
-		r.AddChild(
-			npub,
-			r.CreateNpubDir(r, pointer, nil),
-			true,
-		)
-	}
+	go func() {
+		time.Sleep(time.Millisecond * 100)
 
-	// add ourselves
-	npub, _ := nip19.EncodePublicKey(r.rootPubKey)
-	if r.GetChild(npub) == nil {
-		pointer := nostr.ProfilePointer{PublicKey: r.rootPubKey}
+		// add our contacts
+		fl := r.sys.FetchFollowList(r.ctx, r.rootPubKey)
+		for _, f := range fl.Items {
+			pointer := nostr.ProfilePointer{PublicKey: f.Pubkey, Relays: []string{f.Relay}}
+			npub, _ := nip19.EncodePublicKey(f.Pubkey)
+			r.AddChild(
+				npub,
+				r.CreateNpubDir(r, pointer, nil),
+				true,
+			)
+		}
 
-		r.AddChild(
-			npub,
-			r.CreateNpubDir(r, pointer, r.signer),
-			true,
-		)
-	}
+		// add ourselves
+		npub, _ := nip19.EncodePublicKey(r.rootPubKey)
+		if r.GetChild(npub) == nil {
+			pointer := nostr.ProfilePointer{PublicKey: r.rootPubKey}
 
-	// add a link to ourselves
-	r.AddChild("@me", r.NewPersistentInode(
-		r.ctx,
-		&fs.MemSymlink{Data: []byte(r.wd + "/" + npub)},
-		fs.StableAttr{Mode: syscall.S_IFLNK},
-	), true)
+			r.AddChild(
+				npub,
+				r.CreateNpubDir(r, pointer, r.signer),
+				true,
+			)
+		}
+
+		// add a link to ourselves
+		r.AddChild("@me", r.NewPersistentInode(
+			r.ctx,
+			&fs.MemSymlink{Data: []byte(r.wd + "/" + npub)},
+			fs.StableAttr{Mode: syscall.S_IFLNK},
+		), true)
+	}()
 }
 
 func (r *NostrRoot) Lookup(_ context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
diff --git a/nostrfs/viewdir.go b/nostrfs/viewdir.go
index b4b94a1..838ab05 100644
--- a/nostrfs/viewdir.go
+++ b/nostrfs/viewdir.go
@@ -18,12 +18,13 @@ type ViewDir struct {
 	paginate    bool
 	relays      []string
 	replaceable bool
-	extension   string
+	createable  bool
 }
 
 var (
 	_ = (fs.NodeOpendirer)((*ViewDir)(nil))
 	_ = (fs.NodeGetattrer)((*ViewDir)(nil))
+	_ = (fs.NodeMkdirer)((*ViewDir)(nil))
 )
 
 func (n *ViewDir) Getattr(_ context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
@@ -37,7 +38,7 @@ func (n *ViewDir) Getattr(_ context.Context, f fs.FileHandle, out *fuse.AttrOut)
 	return fs.OK
 }
 
-func (n *ViewDir) Opendir(_ context.Context) syscall.Errno {
+func (n *ViewDir) Opendir(ctx context.Context) syscall.Errno {
 	if n.fetched.CompareAndSwap(true, true) {
 		return fs.OK
 	}
@@ -59,7 +60,6 @@ func (n *ViewDir) Opendir(_ context.Context) syscall.Errno {
 				root:        n.root,
 				filter:      filter,
 				relays:      n.relays,
-				extension:   n.extension,
 				replaceable: n.replaceable,
 			},
 			fs.StableAttr{Mode: syscall.S_IFDIR},
@@ -74,15 +74,39 @@ func (n *ViewDir) Opendir(_ context.Context) syscall.Errno {
 			if name == "" {
 				name = "_"
 			}
-			n.AddChild(name, n.root.CreateEntityDir(n, n.extension, evt), true)
+			if n.GetChild(name) == nil {
+				n.AddChild(name, n.root.CreateEntityDir(n, evt), true)
+			}
 		}
 	} else {
 		for ie := range n.root.sys.Pool.FetchMany(n.root.ctx, n.relays, n.filter,
 			nostr.WithLabel("nakfs"),
 		) {
-			n.AddChild(ie.Event.ID, n.root.CreateEventDir(n, ie.Event), true)
+			if n.GetChild(ie.Event.ID) == nil {
+				n.AddChild(ie.Event.ID, n.root.CreateEventDir(n, ie.Event), true)
+			}
 		}
 	}
 
 	return fs.OK
 }
+
+func (n *ViewDir) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
+	if !n.createable || n.root.signer == nil || n.root.rootPubKey != n.filter.Authors[0] {
+		return nil, syscall.ENOTSUP
+	}
+
+	if n.replaceable {
+		// create a template event that can later be modified and published as new
+		return n.root.CreateEntityDir(n, &nostr.Event{
+			PubKey:    n.root.rootPubKey,
+			CreatedAt: 0,
+			Kind:      n.filter.Kinds[0],
+			Tags: nostr.Tags{
+				nostr.Tag{"d", name},
+			},
+		}), fs.OK
+	}
+
+	return nil, syscall.ENOTSUP
+}
diff --git a/nostrfs/writeablefile.go b/nostrfs/writeablefile.go
index 2c897fb..c37291a 100644
--- a/nostrfs/writeablefile.go
+++ b/nostrfs/writeablefile.go
@@ -48,16 +48,18 @@ func (f *WriteableFile) Open(ctx context.Context, flags uint32) (fh fs.FileHandl
 func (f *WriteableFile) Write(ctx context.Context, fh fs.FileHandle, data []byte, off int64) (uint32, syscall.Errno) {
 	f.mu.Lock()
 	defer f.mu.Unlock()
-	end := int64(len(data)) + off
-	if int64(len(f.data)) < end {
-		n := make([]byte, end)
-		copy(n, f.data)
-		f.data = n
+
+	offset := int(off)
+	end := offset + len(data)
+	if len(f.data) < end {
+		newData := make([]byte, offset+len(data))
+		copy(newData, f.data)
+		f.data = newData
 	}
-	copy(f.data[off:off+int64(len(data))], data)
+	copy(f.data[offset:], data)
+	f.data = f.data[0:end]
 
 	f.onWrite(string(f.data))
-
 	return uint32(len(data)), fs.OK
 }
 
@@ -69,7 +71,7 @@ func (f *WriteableFile) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse
 	return fs.OK
 }
 
-func (f *WriteableFile) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
+func (f *WriteableFile) Setattr(_ context.Context, _ fs.FileHandle, _ *fuse.SetAttrIn, _ *fuse.AttrOut) syscall.Errno {
 	return fs.OK
 }