Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

feat: 索引の射影 — 本文ごと落ちる索引が「次ページ」を書けなくしている

Open
#78 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
35/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Active
Tech stack
typescript

Research direction

Start by reading src/publishChecks.ts, src/firestore/appViews.ts, and the mirror/mirrorOf handling, then inspect the view declarations and src/view/protocol.ts. Define the projection and validation behavior described for article indexes, including same-batch writes and unchanged behavior for existing apps; confirm the relevant checks and repository tests pass.

Written by the indexing model from the issue text.

Description

enhancement

記事を出すアプリ(AI Journal, /a/ai-journal)で「次の10本」を出そうとして、書けないことが分かった。ページ側の問題ではなく、索引がレコードを丸ごと落とす設計の帰結なので、ここに置く。

何が起きたか

索引は最新 10 本しか出せていない。11 本目以降へ送る「次へ」を足そうとして views[].limit を 50 に上げたところ、check が正しく拒否した:

views[0] reads 50 articles whose drawn text may total 61100 bytes each, so one open of the
index costs a reader at least 3055000 bytes — above 1000000 ... the only way to a cheap
index is a second collection carrying title and summary alone.

拒否は正しい。問題は、この拒否から出る道が今ひとつも無いことである。作者に残された選択肢は 3 つで、どれも索引を諦めている:

  1. maxBytes.body を下げる — 索引を深くするために本文の長さを削る。無関係な 2 つが宣言の上で結びついている。
  2. limit を上限(MAX_INDEX_BYTES / Σmaxbytes、body 60,000 なら 16 本)で止める — コレクションは伸び続けるので、古い記事は静かに索引から落ちていく。記事自体は /a/{slug}/{id} で生きているのに、辿り着く道だけが消える。
  3. 拒否文が勧める「title と summary だけの第二のコレクション」を作る — 投稿・修正・削除のたびに 2 レコードを同期する仕掛けをアプリの作者が書く。書き手向けの指示も倍になり、同期は原子的でない。

3 番目が本来の解であり、だからこそプラットフォームの仕事だと思う。

今どうなっているか(確認したこと)

  • 索引の読みはビジターのブラウザで一発。mulmoserver src/firestore/appViews.ts:253-276 — getDocs(query(items, orderBy(stampField,"desc"), limit(n)))。startAfter は import すらされていない。
  • データは push 一方通行。ページからホストに頼める ask は lookup(自分の 1 行)と open(記事へ遷移)だけで、「続きをくれ」は wire に無い(src/view/protocol.ts の VIEW_MESSAGE)。
  • ルールはフィールドを落とせない(原則 5)ので索引は body ごと落ちる。ゆえに MAX_INDEX_BYTES = 1_000_000 の掛け算検査(src/publishChecks.ts、drawnTextFields を使っている箇所)。行数の上限 MAX_VIEW_LIMIT = 1000 は縛っていない。縛っているのはバイト数である。

つまり 3 つが直列に効いている: 射影できない → 索引が高い → 深くできない → ページ送りが書けない。手前から解くべきで、view.page() を先に足しても解決しない。

提案 1(本命): 索引の射影をホストが維持する

宣言したフィールドだけを持つ軽いレコードを、記事の書き込みと同じバッチで索引コレクションに書く。

{
  "collections": {
    "articles": { "index": { "collection": "article-index",
                             "fields": ["title", "summary", "byline", "tags"] } },
    "article-index": { "indexOf": "articles" }
  },
  "views": [
    { "id": "public", "audience": "public", "path": "views/home.html",
      "collections": ["article-index"], "limit": { "article-index": 200 },
      "article": { "collection": "articles", "title": "title", "body": "body", ... } }
  ]
}
  • mirror / mirrorOf が先例である。予約の有無を slot の state に写す 2 文書 1 バッチを、ルールはすでに受けている。あちらは 1 フィールドの状態同期、こちらは宣言したフィールドの複写で、必要な性質(原子性・片割れの禁止・publish が両半分を要求すること)は同じ。だから Cloud Function もトリガも要らない。
  • 索引側の id は記事と同じ(idFrom: "slug" がそのまま効く)。stampField は複写され、limit の順序付けはそのまま。
  • 効果: 1 本あたり約 1,400 バイト(title 200 + summary 800 + byline 100 + tags 200 + slug + stamp)。同じ 1 MB の予算で 約 700 本。本文の長さ制限は 1 バイトも下げなくてよい。 ページ送りはアプリ側のクライアントコードのままで何年も持つ。
  • 検査は減るのではなく移る: 掛け算は索引コレクションの maxBytes に対して行い、article を持つビューが本文を持つコレクションを直接読んでいたら「索引を宣言せよ」と言えるようになる。今の拒否文が代替を持てるようになる、というのがこの提案の核心。

要検討:

  • 削除の片割れ(記事だけ消えて索引が残る)を、mirror と同じくルールで塞げるか。
  • correct が索引フィールドに触れたときの複写。書き込みが 1 バッチである限り同じ話のはず。
  • 既存アプリ: index を書かなければ射影は 1 バイトも変わらないこと。

提案 2(後続): view.page(cid, { before, rows })

提案 1 の後、記事が窓を超えたときに効く。lookup と同じ形で足せる:

  • VIEW_MESSAGE に page / pageResult
  • BridgePorts に optional な page ポート(lookup と同じく、無いホストは「わからない」を返す。mine の「absence は UNKNOWN であって no ではない」約束をそのまま踏襲する)
  • mulmoserver の viewConstraints に startAfter(stampField は文字列順=時刻順なので where(stamp, "<", cursor) でも足りる)

単独では解にならないことを書いておく: 射影が無いままだと「次へ」1 回で最大 60 万バイト落ちる。ただしコストの性質は変わる — 今は全員が毎回払う固定費、pull は押した人だけが払う変動費。なのでバイトの掛け算検査は「初回 push の分だけ」に緩められるはずで、その判断は提案 1 が入ってからでよい。

やらないこと

view.query()(タグ絞り込み・月別アーカイブ・検索)は別の話。索引が軽くなっている(提案 1)ことが前提で、複合インデックスの管理も要る。ここには含めない。

出口

提案 1 は宣言・検査・射影の追加なので、この repo が先頭。mulmoserver 側は索引コレクションを読むだけで、ページ送り自体はアプリの HTML で書ける。提案 2 は wire protocol を跨ぐので、live のときと同じ 3 リポジトリの順番が要る。

Dominant language
TypeScript
Stars
1
Forks
0
Avg merge
9m
Merged PRs (30d)
21

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from receptron/sharedapp

All issues in receptron/sharedapp

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.