Sign inAppearance settingsPlatformAI CODE CREATIONGitHub CopilotWrite better code with AIGitHub Copilot appDirect agents from issue to mergeMCP RegistryIntegrate external toolsDEVELOPER WORKFLOWSActionsAutomate any workflowCodespacesInstant dev environmentsIssuesPlan and track workCode ReviewManage code changesCode QualityEnforce quality at mergeAPPLICATION SECURITYGitHub Advanced SecurityFind and fix vulnerabilitiesCode securitySecure your code as you buildSecret protectionStop leaks before they startEXPLOREWhy GitHubDocumentationBlogChangelogMarketplaceView all featuresSolutionsBY COMPANY SIZEEnterprisesSmall and medium teamsStartupsNonprofitsBY USE CASEApp ModernizationDevSecOpsDevOpsCI/CDView all use casesBY INDUSTRYHealthcareFinancial servicesManufacturingGovernmentView all industriesView all solutionsResourcesEXPLORE BY TOPICAISoftware DevelopmentDevOpsSecurityView all topicsEXPLORE BY TYPECustomer storiesEvents & webinarsEbooks & reportsBusiness insightsGitHub SkillsSUPPORT & SERVICESDocumentationCustomer supportCommunity forumTrust centerPartnersView all resourcesOpen SourceCOMMUNITYGitHub SponsorsFund open source developersPROGRAMSSecurity LabMaintainer CommunityAcceleratorGitHub StarsArchive ProgramREPOSITORIESTopicsTrendingCollectionsEnterpriseENTERPRISE SOLUTIONSEnterprise platformAI-powered developer platformAVAILABLE ADD-ONSGitHub Advanced SecurityEnterprise-grade security featuresCopilot for BusinessEnterprise-grade AI featuresPremium SupportEnterprise-grade 24/7 supportPricingType / to searchSign inSign upAppearance settings You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session. Dismiss alert {{ message }} alin9661 / hnx Public Notifications You must be signed in to change notification settings Fork 0 Star 1 Code Issues 0 Pull requests 0 Actions Projects Security and quality 0 Insights Additional navigation options Code Issues Pull requests Actions Projects Security and quality Insights {"payload":{"codeViewRepoRoute":{"path":"/","refInfo":{"name":"main","listCacheKey":"v0:1786483884.0","canEdit":false,"refType":"branch","currentOid":"c96a9d751052607ec5ddd47336fad246154eade3"},"tree":{"items":[{"name":".github","path":".github","contentType":"directory"},{"name":"benches","path":"benches","contentType":"directory"},{"name":"docs","path":"docs","contentType":"directory"},{"name":"packaging/scoop","path":"packaging/scoop","contentType":"directory","hasSimplifiedPath":true},{"name":"src","path":"src","contentType":"directory"},{"name":"tests","path":"tests","contentType":"directory"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":"ACKNOWLEDGMENTS.md","path":"ACKNOWLEDGMENTS.md","contentType":"file"},{"name":"CHANGELOG.md","path":"CHANGELOG.md","contentType":"file"},{"name":"CONTRIBUTING.md","path":"CONTRIBUTING.md","contentType":"file"},{"name":"Cargo.lock","path":"Cargo.lock","contentType":"file"},{"name":"Cargo.toml","path":"Cargo.toml","contentType":"file"},{"name":"LICENSE-APACHE","path":"LICENSE-APACHE","contentType":"file"},{"name":"LICENSE-MIT","path":"LICENSE-MIT","contentType":"file"},{"name":"README.md","path":"README.md","contentType":"file"},{"name":"SECURITY.md","path":"SECURITY.md","contentType":"file"},{"name":"TODOS.md","path":"TODOS.md","contentType":"file"},{"name":"VERSION","path":"VERSION","contentType":"file"},{"name":"deny.toml","path":"deny.toml","contentType":"file"},{"name":"dist-workspace.toml","path":"dist-workspace.toml","contentType":"file"},{"name":"rust-toolchain.toml","path":"rust-toolchain.toml","contentType":"file"},{"name":"rustfmt.toml","path":"rustfmt.toml","contentType":"file"}],"totalCount":22,"templateDirectorySuggestionUrl":null,"readme":null,"showBranchInfobar":false},"userNameDisplayConfiguration":null,"treeExpanded":false,"symbolsExpanded":false,"copilotSWEAgentEnabled":false,"copilotAccessAllowed":false,"isOverview":true,"overview":{"banners":{"shouldRecommendReadme":false,"isPersonalRepo":false,"showUseActionBanner":false,"actionSlug":null,"actionId":null,"showProtectBranchBanner":false,"requiredCustomPropertyValuesMissingCount":0,"transactionalMessageBanner":null,"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_repo","releasePath":"/alin9661/hnx/releases/new?marketplace=true","showPublishActionBanner":false},"interactionLimitBanner":null,"showInvitationBanner":false,"inviterName":null,"actionsMigrationBannerInfo":{"releaseTags":[],"showImmutableActionsMigrationBanner":false,"initialMigrationStatus":null},"copilotSurveyBanner":null,"showSpammyBanner":false,"blockedContributorsBanner":null,"codespacesSurveyBanner":null},"codeButton":{"contactPath":"/contact","isEnterprise":false,"local":{"protocolInfo":{"httpAvailable":true,"sshAvailable":null,"httpUrl":"https://github.com/alin9661/hnx.git","showCloneWarning":null,"sshUrl":null,"sshCertificatesRequired":null,"sshCertificatesAvailable":null,"ghCliUrl":"gh repo clone alin9661/hnx","defaultProtocol":"http","newSshKeyUrl":"/settings/ssh/new","setProtocolPath":"/users/set_protocol?protocol_type=clone"},"platformInfo":{"cloneUrl":"https://desktop.github.com","showVisualStudioCloneButton":false,"visualStudioCloneUrl":"https://windows.github.com","showXcodeCloneButton":false,"xcodeCloneUrl":"xcode://clone?repo=https%3A%2F%2Fgithub.com%2Falin9661%2Fhnx","zipballUrl":"/alin9661/hnx/archive/refs/heads/main.zip"}},"newCodespacePath":"/codespaces/new?hide_repo_select=true\u0026repo=1329942061"},"popovers":{"rename":null,"renamedParentRepo":null},"commitCount":"87","overviewFiles":[{"displayName":"README.md","repoName":"hnx","refName":"main","path":"README.md","preferredFileType":"readme","tabName":"README","richText":"\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch1 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003ehnx\u003c/h1\u003e\u003ca id=\"user-content-hnx\" class=\"anchor\" aria-label=\"Permalink: hnx\" href=\"#hnx\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003ehnx\u003c/code\u003e is a fast, cache-first Hacker News client for the terminal. Installing\nthe crate creates the \u003ccode\u003ehnx\u003c/code\u003e command.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eIt combines an adaptive Ratatui interface with stable text and JSON commands.\nCached results render immediately, refresh asynchronously, and remain usable\noffline with visible freshness and source metadata.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eWhy a new Rust client?\u003c/h2\u003e\u003ca id=\"user-content-why-a-new-rust-client\" class=\"anchor\" aria-label=\"Permalink: Why a new Rust client?\" href=\"#why-a-new-rust-client\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eRust improves cold start, resident memory, packaging, and terminal safety. The\nlargest performance gain, however, comes from changing request topology:\nFirebase supplies canonical ranked IDs, Algolia fills most records in one\nbatch, and bounded Firebase fan-out fills only the gaps. Comment traversal is\ncycle-safe and capped by time, depth, node count, response size, and at most 12\nconcurrent requests.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eOn the development Mac, the legacy request shape took 3.35–3.67 seconds for 30\nserial Firebase story requests. The bounded concurrent equivalent took\n0.39–0.45 seconds (about 8.5x faster), and one Algolia front-page request took\n0.22–0.41 seconds. These are directional measurements, not universal network\nguarantees; the reproducible budgets live in \u003ca href=\"/alin9661/hnx/blob/main/docs/PERFORMANCE.md\"\u003edocs/PERFORMANCE.md\u003c/a\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eInstall\u003c/h2\u003e\u003ca id=\"user-content-install\" class=\"anchor\" aria-label=\"Permalink: Install\" href=\"#install\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eDuring development:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"cargo install --path .\"\u003e\u003cpre\u003ecargo install --path \u003cspan class=\"pl-c1\"\u003e.\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eAfter publication:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"cargo install hnx\"\u003e\u003cpre\u003ecargo install hnx\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eOr install the latest release binary on macOS or Linux:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"bash -o pipefail -c 'curl --proto \u0026quot;=https\u0026quot; --tlsv1.2 -LsSf https://github.com/alin9661/hnx/releases/latest/download/hnx-installer.sh | sh'\"\u003e\u003cpre\u003ebash -o pipefail -c \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e'\u003c/span\u003ecurl --proto \"=https\" --tlsv1.2 -LsSf https://github.com/alin9661/hnx/releases/latest/download/hnx-installer.sh | sh\u003cspan class=\"pl-pds\"\u003e'\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003epipefail\u003c/code\u003e makes a failed download fail the full install command instead of\nletting an empty \u003ccode\u003esh\u003c/code\u003e invocation report success.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eUse\u003c/h2\u003e\u003ca id=\"user-content-use\" class=\"anchor\" aria-label=\"Permalink: Use\" href=\"#use\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eLaunch the interactive client:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"hnx\"\u003e\u003cpre\u003ehnx\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eUse the headless interface:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"hnx feed top --limit 30\nhnx feed jobs --format json\nhnx item 8863 --comments\nhnx search \u0026quot;rust terminal\u0026quot; --type story --format json\nhnx hiring rust\nhnx freelance design\nhnx --offline feed top\nhnx cache stats\"\u003e\u003cpre\u003ehnx feed top --limit 30\nhnx feed \u003cspan class=\"pl-c1\"\u003ejobs\u003c/span\u003e --format json\nhnx item 8863 --comments\nhnx search \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003erust terminal\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003e\u003c/span\u003e --type story --format json\nhnx hiring rust\nhnx freelance design\nhnx --offline feed top\nhnx cache stats\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGlobal options include \u003ccode\u003e--offline\u003c/code\u003e, \u003ccode\u003e--theme \u0026lt;name-or-path\u0026gt;\u003c/code\u003e, \u003ccode\u003e--config \u0026lt;path\u0026gt;\u003c/code\u003e,\n\u003ccode\u003e--layout two[:STORIES]\u003c/code\u003e, \u003ccode\u003e--layout three[:STORIES,THREAD]\u003c/code\u003e, and\n\u003ccode\u003e--layout reset\u003c/code\u003e. Run \u003ccode\u003ehnx --help\u003c/code\u003e for the complete command surface.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eJSON output has a versioned envelope:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-json notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"{\n \u0026quot;schema_version\u0026quot;: 1,\n \u0026quot;source\u0026quot;: \u0026quot;algolia\u0026quot;,\n \u0026quot;stale\u0026quot;: false,\n \u0026quot;fetched_at\u0026quot;: 1786334400,\n \u0026quot;data\u0026quot;: []\n}\"\u003e\u003cpre\u003e{\n \u003cspan class=\"pl-ent\"\u003e\"schema_version\"\u003c/span\u003e: \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e,\n \u003cspan class=\"pl-ent\"\u003e\"source\"\u003c/span\u003e: \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003ealgolia\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003e\u003c/span\u003e,\n \u003cspan class=\"pl-ent\"\u003e\"stale\"\u003c/span\u003e: \u003cspan class=\"pl-c1\"\u003efalse\u003c/span\u003e,\n \u003cspan class=\"pl-ent\"\u003e\"fetched_at\"\u003c/span\u003e: \u003cspan class=\"pl-c1\"\u003e1786334400\u003c/span\u003e,\n \u003cspan class=\"pl-ent\"\u003e\"data\"\u003c/span\u003e: []\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eExit status \u003ccode\u003e0\u003c/code\u003e means usable output, including explicitly marked stale cache;\n\u003ccode\u003e2\u003c/code\u003e means invalid input; \u003ccode\u003e3\u003c/code\u003e means the requested data was unavailable from\nboth cache and network. Thread JSON includes \u003ccode\u003edata.metadata.partial\u003c/code\u003e, loaded,\ndeclared, omitted, and unresolved counts. Stdout contains only requested data.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eTUI\u003c/h2\u003e\u003ca id=\"user-content-tui\" class=\"anchor\" aria-label=\"Permalink: TUI\" href=\"#tui\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe default two-pane interface adapts without discarding loaded data,\nselection, folded comments, offsets, or article scroll state:\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e80 columns and wider: story list plus the active thread or detail pane.\u003c/li\u003e\n\u003cli\u003eBelow 80 columns: one focused pane.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp dir=\"auto\"\u003ePress \u003ccode\u003eL\u003c/code\u003e for the optional three-pane Stories → Thread → Detail view. It uses\nthree panes at 120 columns and wider, falls back to the configured two-pane\nview from 80–119 columns, then to the focused pane below 80 columns. Any layout\nfalls back further rather than rendering a pane narrower than 18 columns.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eUse arrow keys or \u003ccode\u003ej\u003c/code\u003e/\u003ccode\u003ek\u003c/code\u003e to move, \u003ccode\u003eCtrl+U\u003c/code\u003e/\u003ccode\u003eCtrl+D\u003c/code\u003e to move half a viewport,\nand Page Up/Page Down to move a full viewport. \u003ccode\u003eTab\u003c/code\u003e/\u003ccode\u003eBackTab\u003c/code\u003e cycles visible\npanes; \u003ccode\u003eh\u003c/code\u003e/\u003ccode\u003el\u003c/code\u003e moves spatially and stops at the edges. \u003ccode\u003eAlt+h\u003c/code\u003e/\u003ccode\u003eAlt+l\u003c/code\u003e shrinks\nor grows the focused pane by two percentage points, and \u003ccode\u003eAlt+0\u003c/code\u003e clears saved\nlive overrides. \u003ccode\u003et\u003c/code\u003e selects the thread and \u003ccode\u003ed\u003c/code\u003e selects detail. \u003ccode\u003eEnter\u003c/code\u003e loads a\nthread or folds a comment, \u003ccode\u003e/\u003c/code\u003e searches, \u003ccode\u003ef\u003c/code\u003e applies a\ncase-insensitive regex filter, \u003ccode\u003eb\u003c/code\u003e bookmarks, \u003ccode\u003ea\u003c/code\u003e reads an article in-terminal,\n\u003ccode\u003eo\u003c/code\u003e opens a link, \u003ccode\u003eO\u003c/code\u003e toggles offline mode, \u003ccode\u003er\u003c/code\u003e refreshes, and \u003ccode\u003en\u003c/code\u003e/\u003ccode\u003ep\u003c/code\u003e load the\nnext/previous 30-story page. Opening a story marks it read; \u003ccode\u003em\u003c/code\u003e toggles\nread/unread, and read state persists across launches. \u003ccode\u003e?\u003c/code\u003e opens help, and \u003ccode\u003eq\u003c/code\u003e\nquits or closes help. The status line always identifies offline/stale/partial\ndata and its source. Story titles and comment bodies reflow to their live pane\nwidths; compact rank and marker gutters keep points aligned beneath each title.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe default \u003ccode\u003eclassic\u003c/code\u003e theme uses Y Combinator orange (\u003ccode\u003e#FF6600\u003c/code\u003e) and Hacker\nNews cream (\u003ccode\u003e#F7F6F0\u003c/code\u003e). \u003ccode\u003emidnight\u003c/code\u003e, an ANSI 16-color theme, \u003ccode\u003eNO_COLOR\u003c/code\u003e, and\ncustom semantic TOML themes are supported.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eLayout preferences are loaded in this order: explicit CLI, saved SQLite\n\u003ccode\u003elayout.v1\u003c/code\u003e state, platform \u003ccode\u003ehnx/config.toml\u003c/code\u003e, then built-in defaults. The\nplatform file accepts:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-toml notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"[layout]\nmode = \u0026quot;two\u0026quot;\ntwo = [44, 56]\nthree = [38, 34, 28]\ntwo_min_width = 80\nthree_min_width = 120\"\u003e\u003cpre\u003e[\u003cspan class=\"pl-en\"\u003elayout\u003c/span\u003e]\n\u003cspan class=\"pl-smi\"\u003emode\u003c/span\u003e = \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003etwo\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003e\u003c/span\u003e\n\u003cspan class=\"pl-smi\"\u003etwo\u003c/span\u003e = [\u003cspan class=\"pl-c1\"\u003e44\u003c/span\u003e, \u003cspan class=\"pl-c1\"\u003e56\u003c/span\u003e]\n\u003cspan class=\"pl-smi\"\u003ethree\u003c/span\u003e = [\u003cspan class=\"pl-c1\"\u003e38\u003c/span\u003e, \u003cspan class=\"pl-c1\"\u003e34\u003c/span\u003e, \u003cspan class=\"pl-c1\"\u003e28\u003c/span\u003e]\n\u003cspan class=\"pl-smi\"\u003etwo_min_width\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003e80\u003c/span\u003e\n\u003cspan class=\"pl-smi\"\u003ethree_min_width\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003e120\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eAn explicitly requested invalid config exits with status 2. Invalid automatic\nor saved preferences fall back as a complete unit and produce one status-line\nwarning. \u003ccode\u003e--layout reset\u003c/code\u003e removes the saved override so TOML changes take\neffect again.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eDevelopment\u003c/h2\u003e\u003ca id=\"user-content-development\" class=\"anchor\" aria-label=\"Permalink: Development\" href=\"#development\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe project uses Rust 2024 with MSRV 1.88 and pins the development toolchain.\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"cargo fmt --all -- --check\ncargo clippy --all-targets --all-features -- -D warnings\ncargo test --all-targets\ncargo bench\"\u003e\u003cpre\u003ecargo fmt --all -- --check\ncargo clippy --all-targets --all-features -- -D warnings\ncargo \u003cspan class=\"pl-c1\"\u003etest\u003c/span\u003e --all-targets\ncargo bench\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eProject guides: \u003ca href=\"/alin9661/hnx/blob/main/docs/ARCHITECTURE.md\"\u003earchitecture\u003c/a\u003e,\n\u003ca href=\"/alin9661/hnx/blob/main/docs/CACHING.md\"\u003ecache design\u003c/a\u003e, \u003ca href=\"/alin9661/hnx/blob/main/docs/DEPENDENCIES.md\"\u003edependencies\u003c/a\u003e,\n\u003ca href=\"/alin9661/hnx/blob/main/docs/PERFORMANCE.md\"\u003eperformance\u003c/a\u003e,\n\u003ca href=\"/alin9661/hnx/blob/main/docs/THEMES.md\"\u003ethemes\u003c/a\u003e, \u003ca href=\"/alin9661/hnx/blob/main/docs/RELEASING.md\"\u003ereleasing\u003c/a\u003e,\n\u003ca href=\"/alin9661/hnx/blob/main/CONTRIBUTING.md\"\u003econtributing\u003c/a\u003e, \u003ca href=\"/alin9661/hnx/blob/main/TODOS.md\"\u003efollow-up work\u003c/a\u003e, the \u003ca href=\"/alin9661/hnx/blob/main/SECURITY.md\"\u003esecurity\npolicy\u003c/a\u003e, and the \u003ca href=\"/alin9661/hnx/blob/main/CHANGELOG.md\"\u003echangelog\u003c/a\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eProvenance and naming\u003c/h2\u003e\u003ca id=\"user-content-provenance-and-naming\" class=\"anchor\" aria-label=\"Permalink: Provenance and naming\" href=\"#provenance-and-naming\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThis is an original clean-room implementation informed by the public behavior\nof the projects listed in \u003ca href=\"/alin9661/hnx/blob/main/ACKNOWLEDGMENTS.md\"\u003eACKNOWLEDGMENTS.md\u003c/a\u003e. No source\nfrom those projects is included.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eBoth the project and its command are named \u003ccode\u003ehnx\u003c/code\u003e. That is deliberate:\n\u003ca href=\"https://github.com/donnemartin/haxor-news\"\u003ehaxor-news\u003c/a\u003e already installs a \u003ccode\u003ehn\u003c/code\u003e\ncommand, and shipping a second binary under the same name would collide on\n\u003ccode\u003ePATH\u003c/code\u003e and invite confusion about which project a user is running. \u003ccode\u003ehnx\u003c/code\u003e is\ndistinct at every layer — crate name, command name, and repository.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eCopyright is not the concern here. The\n\u003ca href=\"https://www.copyright.gov/help/faq/faq-protect.html\" rel=\"nofollow\"\u003eU.S. Copyright Office\u003c/a\u003e\nstates that names and short phrases are not protected by copyright; it applies\nto original expression such as source code, and none of that is shared. The\nupstream project is\n\u003ca href=\"https://github.com/donnemartin/haxor-news/blob/master/LICENSE.txt\"\u003eApache-2.0 licensed\u003c/a\u003e,\nand \u003ca href=\"https://www.apache.org/licenses/LICENSE-2.0\" rel=\"nofollow\"\u003eApache-2.0 section 6\u003c/a\u003e does not\ngrant general trademark rights, so naming can still raise trademark or\npassing-off questions independent of copyright. Using a distinct command name\nand stating clearly that this project is independent and unaffiliated addresses\nboth. This is practical U.S.-focused project guidance, not legal advice.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eLicense\u003c/h2\u003e\u003ca id=\"user-content-license\" class=\"anchor\" aria-label=\"Permalink: License\" href=\"#license\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eLicensed under either \u003ca href=\"/alin9661/hnx/blob/main/LICENSE-APACHE\"\u003eApache-2.0\u003c/a\u003e or \u003ca href=\"/alin9661/hnx/blob/main/LICENSE-MIT\"\u003eMIT\u003c/a\u003e, at\nyour option. \u003ccode\u003ehnx\u003c/code\u003e is independent and is not affiliated with or endorsed by\nY Combinator.\u003c/p\u003e\n\u003c/article\u003e","loaded":true,"timedOut":false,"errorMessage":null,"headerInfo":{"toc":[{"level":1,"text":"hnx","anchor":"hnx","htmlText":"hnx"},{"level":2,"text":"Why a new Rust client?","anchor":"why-a-new-rust-client","htmlText":"Why a new Rust client?"},{"level":2,"text":"Install","anchor":"install","htmlText":"Install"},{"level":2,"text":"Use","anchor":"use","htmlText":"Use"},{"level":2,"text":"TUI","anchor":"tui","htmlText":"TUI"},{"level":2,"text":"Development","anchor":"development","htmlText":"Development"},{"level":2,"text":"Provenance and naming","anchor":"provenance-and-naming","htmlText":"Provenance and naming"},{"level":2,"text":"License","anchor":"license","htmlText":"License"}],"siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Falin9661%2Fhnx"}},{"displayName":"CONTRIBUTING.md","repoName":"hnx","refName":"main","path":"CONTRIBUTING.md","preferredFileType":"contributing","tabName":"Contributing","richText":null,"loaded":false,"timedOut":false,"errorMessage":null,"headerInfo":{"toc":null,"siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Falin9661%2Fhnx"}},{"displayName":"LICENSE-APACHE","repoName":"hnx","refName":"main","path":"LICENSE-APACHE","preferredFileType":"license","tabName":"Apache-2.0","richText":null,"loaded":false,"timedOut":false,"errorMessage":null,"headerInfo":{"toc":null,"siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Falin9661%2Fhnx"}},{"displayName":"LICENSE-MIT","repoName":"hnx","refName":"main","path":"LICENSE-MIT","preferredFileType":"license","tabName":"MIT","richText":null,"loaded":false,"timedOut":false,"errorMessage":null,"headerInfo":{"toc":null,"siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Falin9661%2Fhnx"}},{"displayName":"SECURITY.md","repoName":"hnx","refName":"main","path":"SECURITY.md","preferredFileType":"security","tabName":"Security","richText":null,"loaded":false,"timedOut":false,"errorMessage":null,"headerInfo":{"toc":null,"siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Falin9661%2Fhnx"}}],"overviewFilesProcessingTime":0,"copilotSWEAgentEnabled":false}},"codeViewLayoutRoute":{"repo":{"id":1329942061,"defaultBranch":"main","name":"hnx","ownerLogin":"alin9661","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2026-08-10T14:57:30.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/90729424?v=4","public":true,"private":false,"isOrgOwned":false},"currentUser":null,"uploadToken":"zJqPYUFWyYXC9ayX3aCE2cmT0ZyXHhY7vhuIzxEQ1u-UXJVObJ2tw-OyFAdTiIOHOhlqXxNIYyo0uAarex4xLw","allShortcutsEnabled":false,"treeExpanded":true,"path":"/","symbolsExpanded":false,"refInfo":{"name":"main","listCacheKey":"v0:1786483884.0","canEdit":false,"currentOid":"c96a9d751052607ec5ddd47336fad246154eade3"},"helpUrl":"https://docs.github.com","findFileWorkerPath":"/assets-cdn/worker/find-file-worker-197357bd459e1d17.js","findInFileWorkerPath":"/assets-cdn/worker/find-in-file-worker-cdd287749939f30b.js","githubDevUrl":null},"sidebarAbout":{"topics":[],"showInsights":true,"canOpenStargazersAndWatchers":false,"stargazerCount":1,"watcherCount":0,"forksCount":0,"stargazersPath":"/alin9661/hnx/stargazers","watchersPath":"/alin9661/hnx/watchers","forkNetworkPath":"/alin9661/hnx/forks","activityPath":"/alin9661/hnx/activity","reportPath":"/contact/report-content?content_url=https%3A%2F%2Fgithub.com%2Falin9661%2Fhnx\u0026report=alin9661+%28user%29","canEditMetadata":false,"ownerLogin":"alin9661","repoName":"hnx","isOrg":false,"sections":{"releases":{"releaseCount":2,"tagCount":2},"sponsors":false,"deployments":false,"packages":true,"usedBy":false,"contributors":true,"languages":true,"cta":false,"suggestedWorkflows":false},"hasCitation":false,"repo":{"ownerAvatarUrl":"https://avatars.githubusercontent.com/u/90729424?s=60\u0026v=4","ownerId":90729424,"isPrivate":false,"isTemplate":false,"isFork":false,"isMirror":false,"isArchived":false,"visibilityLabel":"Public","license":{"spdxId":"Apache-2.0","name":"Apache License 2.0"},"isAdvisoryWorkspace":false},"star":{"viewerHasStarred":false,"canStar":false},"fork":{"canFork":false,"forkabilityError":"not_logged_in"},"pin":{"canPin":false,"isPinned":false,"isOrgOwned":false,"pinItemsRemaining":0},"watch":{"canWatch":false,"watchData":{"repositoryId":"1329942061","repositoryName":"alin9661/hnx","watchersCount":0,"subscriptionType":"none","subscribableThreadTypes":[{"name":"Issue","enabled":true,"subscribed":false},{"name":"PullRequest","enabled":true,"subscribed":false},{"name":"Release","enabled":true,"subscribed":false},{"name":"Discussion","enabled":false,"subscribed":false},{"name":"SecurityAlert","enabled":true,"subscribed":false}],"showLabelSubscriptions":false,"subscribedLabels":[]}},"viewer":{"isLoggedIn":false,"isSiteAdmin":false,"emuContributionBlocked":false},"showSponsorButton":false,"showTemplateButton":false,"showDemoNotification":false},"csrf_tokens":{"/alin9661/hnx/branches":{"post":"oldVukhXD0I0BVZj30FCM91oevf72Q48Isc_NDe1ehRdDJJ54xF9njwmCIQMp6bFUPaZ-i2d6fczGCW_MQ3QqQ"}}},"title":"GitHub - alin9661/hnx","appPayload":{},"meta":{"title":"GitHub - alin9661/hnx"}} mainBranchesTagsGo to fileCodeOpen more actions menuFolders and filesNameNameLast commit messageLast commit dateLatest commit History87 Commits87 Commits.github.github benchesbenches docsdocs packaging/scooppackaging/scoop srcsrc teststests .gitignore.gitignore ACKNOWLEDGMENTS.mdACKNOWLEDGMENTS.md CHANGELOG.mdCHANGELOG.md CONTRIBUTING.mdCONTRIBUTING.md Cargo.lockCargo.lock Cargo.tomlCargo.toml LICENSE-APACHELICENSE-APACHE LICENSE-MITLICENSE-MIT README.mdREADME.md SECURITY.mdSECURITY.md TODOS.mdTODOS.md VERSIONVERSION deny.tomldeny.toml dist-workspace.tomldist-workspace.toml rust-toolchain.tomlrust-toolchain.toml rustfmt.tomlrustfmt.toml View all filesRepository files navigationREADMEContributingApache-2.0 licenseMIT licenseSecurityMore itemshnx hnx is a fast, cache-first Hacker News client for the terminal. Installing the crate creates the hnx command.
It combines an adaptive Ratatui interface with stable text and JSON commands. Cached results render immediately, refresh asynchronously, and remain usable offline with visible freshness and source metadata.
Rust improves cold start, resident memory, packaging, and terminal safety. The largest performance gain, however, comes from changing request topology: Firebase supplies canonical ranked IDs, Algolia fills most records in one batch, and bounded Firebase fan-out fills only the gaps. Comment traversal is cycle-safe and capped by time, depth, node count, response size, and at most 12 concurrent requests.
On the development Mac, the legacy request shape took 3.35–3.67 seconds for 30 serial Firebase story requests. The bounded concurrent equivalent took 0.39–0.45 seconds (about 8.5x faster), and one Algolia front-page request took 0.22–0.41 seconds. These are directional measurements, not universal network guarantees; the reproducible budgets live in docs/PERFORMANCE.md.
bash -o pipefail -c 'curl --proto "=https" --tlsv1.2 -LsSf https://github.com/alin9661/hnx/releases/latest/download/hnx-installer.sh | sh' pipefail makes a failed download fail the full install command instead of letting an empty sh invocation report success.
This article was aggregated automatically by CyberWire Daily's newsfeed engine. Original reporting: github.com.