diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e4a0063..8da2f71 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2026-10-24 23:19:23 UTC using RuboCop version 1.92.1. +# on 2026-10-25 15:08:27 UTC using RuboCop version 1.92.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -72,7 +72,7 @@ RSpec/ContextWording: - 'spec/models/team_spec.rb' - 'spec/support/api/endpoints/it_behaves_like_a_cursor_api.rb' -# Offense count: 9 +# Offense count: 13 # Configuration parameters: CountAsOne. RSpec/ExampleLength: Max: 11 @@ -94,7 +94,7 @@ RSpec/LetSetup: RSpec/MessageSpies: EnforcedStyle: receive -# Offense count: 23 +# Offense count: 29 RSpec/MultipleExpectations: Max: 6 @@ -128,7 +128,7 @@ RSpec/SpecFilePathFormat: - 'spec/commands/whoami_spec.rb' - 'spec/version_spec.rb' -# Offense count: 9 +# Offense count: 11 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings. # URISchemes: http, https diff --git a/CHANGELOG.md b/CHANGELOG.md index 41bd662..192f87c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### Changelog +* 2026/10/25: [#87](https://github.com/slack-ruby/slack-shellbot/pull/87): Redesign the Shell website with shared responsive layouts, charcoal ribbon artwork, a black-bordered animated demo, an "Is this a joke?" FAQ linked from the homepage, and recommendations with original logos for Slava, Strada, Gamebot, and S'Up above Open source while preserving Slack installation and status flows - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent). * 2026/10/24: [#86](https://github.com/slack-ruby/slack-shellbot/pull/86): Upgrade slack-ruby-bot-server to 2.2.1 to fix Grape error handling for unsupported requests and failing health checks - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent). * 2026/10/23: Upgrade to Ruby 4.0.9 and refresh dependencies and CI browser tooling - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent). * 2025/09/29: Upgraded to Ruby 3.4.6 - [@dblock](https://github.com/dblock). diff --git a/README.md b/README.md index dde69fd..007274a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,12 @@ A shell bot for Slack. A demo is worth a thousand words. This bot is built with [slack-ruby-bot-server-events](https://github.com/slack-ruby/slack-ruby-bot-server-events). See [CONTRIBUTING](CONTRIBUTING.md). +## Website artwork + +The website follows the shared Slava, Strata, and Strada layout with a terminal-inspired black/charcoal palette (base `#303035`). The editable `public/img/screenshot-background.svg` adapts the shared ribbon artwork and keeps `preserveAspectRatio="none"` on a fixed viewport-sized layer so expanding help does not resize the background. Preserve the original transparent `public/img/icon.png` terminal logo, its matching multi-resolution favicon, and the original `public/img/shell.gif` demonstration when updating the design. + +The bot recommendations use local copies of the original logos: `bot-slava.png` and `bot-strada.png` from their respective projects' `public/img/icon.png`, `bot-gamebot.png` from slack-gamebot2's `public/img/game.png`, and `bot-sup.png` from slack-sup2's `public/img/icon.png`. Preserve their original colors rather than applying the Shell website's monochrome styling. + ## Copyright and License Copyright (c) 2016-2025, Daniel Doubrovkine, Artsy and [Contributors](CHANGELOG.md). diff --git a/public/favicon.ico b/public/favicon.ico index ecb6681..757b324 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/help.html.erb b/public/help.html.erb index 1c06e43..f455b31 100644 --- a/public/help.html.erb +++ b/public/help.html.erb @@ -1,18 +1,49 @@ - + + Shell: Help <%= partial 'public/partials/_head.html.erb' %> - -

- -

-
-
-        <%= CGI.escapeHTML(SlackShellbot::Commands::Man::HELP) %>
-      
-
+ + <%= partial 'public/partials/_navigation.html.erb' %> +
+
+

Shell: Help

+

A little help for your channel's command line.

+
+
+

Get started

+
    +
  1. <%= partial 'public/partials/_install_button.html.erb' %> and authorize Shell for your workspace.
  2. +
  3. Invite @sh into a Slack channel.
  4. +
  5. Type man to see the commands, or ls to list files.
  6. +
+

Each channel has its own rudimentary file system. This is a shell bot inside Slack, not a connection to your computer's shell.

+
+
+

Commands & questions

+
+ Is this a joke? +

Yes. Also, it works! Shell is a tiny Unix-like shell inside Slack, because apparently Slack needed a command line. Its commands and per-channel file system really work, but it is a toy, not a remote terminal.

+
+
+ Command reference +
<%= CGI.escapeHTML(SlackShellbot::Commands::Man::HELP) %>
+
+
+ How do I create and read a file? +

Try touch notes to create a file, ls to see it, and cat notes to read its contents. Use mkdir projects to create a directory.

+
+
+ How do I leave the editor? +

The basic vi-like editor updates messages in place. Use :wq to quit and save, or :q to quit without saving.

+
+
+
+

Need a hand?

+

Contact dblock@vestris.com, DM @playplayio, or report an issue on GitHub.

+
+
<%= partial 'public/partials/_footer.html.erb' %> - diff --git a/public/img/bot-gamebot.png b/public/img/bot-gamebot.png new file mode 100644 index 0000000..20bd285 Binary files /dev/null and b/public/img/bot-gamebot.png differ diff --git a/public/img/bot-slava.png b/public/img/bot-slava.png new file mode 100644 index 0000000..89e5d33 Binary files /dev/null and b/public/img/bot-slava.png differ diff --git a/public/img/bot-strada.png b/public/img/bot-strada.png new file mode 100644 index 0000000..ad53108 Binary files /dev/null and b/public/img/bot-strada.png differ diff --git a/public/img/bot-sup.png b/public/img/bot-sup.png new file mode 100644 index 0000000..f4f96a2 Binary files /dev/null and b/public/img/bot-sup.png differ diff --git a/public/img/screenshot-background.svg b/public/img/screenshot-background.svg new file mode 100644 index 0000000..502821d --- /dev/null +++ b/public/img/screenshot-background.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/index.html.erb b/public/index.html.erb index 546a37f..31d3f68 100644 --- a/public/index.html.erb +++ b/public/index.html.erb @@ -1,35 +1,83 @@ - + + Shell: A Unix Shell in Every Slack Channel <%= partial 'public/partials/_head.html.erb' %> - - - + - -

- -

-

-

Whoa, a bash shell inside Slack!

-

-

- Creates a rudimentary file system in every channel.
Invite the bot into a channel, try man, ls, and other unix commands. -

-

 

-

-

- Add to Slack -

-

 

-

 

-

- -

+ + <%= partial 'public/partials/_navigation.html.erb' %> +
+
+
+ +
+

Shell for Slack

+

Whoa, a bash shell inside Slack!

+

A rudimentary file system in every channel. Invite the bot into a channel, try man, ls, and other Unix commands.

+
+

<%= partial 'public/partials/_install_button.html.erb' %>

+ Get started + Is this a joke? +
+

+

 

+
+
+
+ Shell running Unix commands inside a Slack channel +
A little command line, right where your team works.
+
+
+
+

A shell in every channel

+

Your familiar commands. A shared space to try them.

+
    +
  1. Add Shell to Slack

    Connect your workspace using Add to Slack.

  2. +
  3. Invite @sh

    Invite the bot into a channel to create its own rudimentary file system.

  4. +
  5. Try a command

    Start with man or ls. Create files, explore directories, or try the basic vi editor.

  6. +
+ Explore the commands +
+
+

More bots for Slack and Discord

+

A little more play, a little more connection.

+ +
+
+

Open source

+

Shell is a shell bot for Slack, built with slack-ruby-bot-server-events. Explore the code, report an issue, or contribute on GitHub.

+

<%= partial 'public/partials/_install_button.html.erb' %>

+
+
<%= partial 'public/partials/_footer.html.erb' %> - diff --git a/public/partials/_footer.html.erb b/public/partials/_footer.html.erb index 86a5fee..c3dcf59 100644 --- a/public/partials/_footer.html.erb +++ b/public/partials/_footer.html.erb @@ -1,8 +1,12 @@ -

+

diff --git a/public/partials/_head.html.erb b/public/partials/_head.html.erb index c8cb63b..95e1cf8 100644 --- a/public/partials/_head.html.erb +++ b/public/partials/_head.html.erb @@ -1,4 +1,7 @@ + + + - + diff --git a/public/partials/_install_button.html.erb b/public/partials/_install_button.html.erb new file mode 100644 index 0000000..6c3f55c --- /dev/null +++ b/public/partials/_install_button.html.erb @@ -0,0 +1 @@ +Add to Slack diff --git a/public/partials/_navigation.html.erb b/public/partials/_navigation.html.erb new file mode 100644 index 0000000..9479836 --- /dev/null +++ b/public/partials/_navigation.html.erb @@ -0,0 +1,12 @@ + + diff --git a/public/privacy.html.erb b/public/privacy.html.erb index d1677dc..bf7bb5c 100644 --- a/public/privacy.html.erb +++ b/public/privacy.html.erb @@ -6,14 +6,10 @@ <%= partial 'public/partials/_head.html.erb' %> - -

- -

-

+ + <%= partial 'public/partials/_navigation.html.erb' %> +

Shell: Privacy Policy

-

-

tl;dr

Shell does not want to know who you are and tries to collect just enough data to enable our services. Please contact dblock@vestris.com for any questions about this privacy policy. @@ -79,7 +75,7 @@

If you have any questions or concerns or complaints about our Privacy Policy or our data collection or processing practices, or if you want to report any security violations to us, please message @dblockdotorg on Twitter or e-mail dblock@vestris.com.

-
+
<%= partial 'public/partials/_footer.html.erb' %> diff --git a/public/styles.css b/public/styles.css index 1ed21f9..8062f43 100644 --- a/public/styles.css +++ b/public/styles.css @@ -1,31 +1,137 @@ +html { + scrollbar-gutter: stable; +} + body { - font-family: 'Arial', 'Helvetica', sans-serif; - text-align: center; - margin: 20px; + font-family: Arial, Helvetica, sans-serif; } -.icon img { - width: 200px; +.site-page { + position: relative; + isolation: isolate; + display: flex; + flex-direction: column; + margin: 0; + min-height: 100vh; + color: #303035; + line-height: 1.7; + text-align: center; + background: #fff; } -.logo img { - width: 120px; +.site-page::before { + position: fixed; + inset: 0; + z-index: -1; + content: ""; + pointer-events: none; + background: linear-gradient(#ffffff66, #ffffff66), url("/img/screenshot-background.svg") center top / 100% 100% no-repeat; } -.image { - max-width: 560px; - margin: auto; +.site-page > main { flex: 1; } +.site-page * { box-sizing: border-box; } +.site-page a { color: #202024; text-underline-offset: 3px; } +.site-page a:hover { text-decoration-thickness: 2px; } +.site-page :focus-visible { outline: 3px solid #44444d; outline-offset: 5px; } +.site-page [id] { scroll-margin-top: 96px; } + +.page-container { + width: calc(100% - 48px); + max-width: 1120px; + margin: 0 auto; } -.image img { - width: 100%; +.narrow-container { max-width: 760px; } +.skip-link { position: absolute; top: -80px; left: 16px; z-index: 20; padding: 10px 16px; background: #fff; } +.skip-link:focus { top: 12px; } +.site-nav { position: sticky; top: 0; z-index: 10; border-bottom: 1px solid #25252b20; background: #ffffffe0; backdrop-filter: blur(12px); } +.nav-inner { display: flex; align-items: center; justify-content: space-between; gap: 24px; min-height: 80px; } +.site-brand { font: italic 700 32px Georgia, serif; text-decoration: none; } +.nav-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px 28px; } +.nav-links a { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-decoration: none; text-transform: uppercase; } +.hero { padding-top: 60px; } +.hero-intro { display: flex; align-items: flex-start; gap: 32px; max-width: 920px; margin: 0 auto; text-align: left; } +.hero-logo { flex: 0 0 auto; width: 112px; height: auto; margin-top: 12px; } +.site-page h1 { margin: 0; color: #17171b; font-size: 48px; letter-spacing: -.04em; line-height: 1.15; } +.hero-tagline { margin: 16px 0; font: italic 30px/1.35 Georgia, serif; } +.hero-description { max-width: 680px; margin: 0 0 24px; font-size: 18px; } +.hero-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; } +#register { margin: 0; } + +.site-page .install-button { + display: inline-flex; + align-items: center; + justify-content: center; + max-width: 100%; + min-height: 44px; + padding: 8px 20px; + border-radius: 5px; + color: #fff; + background: #222226; + font-size: 16px; + font-weight: 700; + line-height: 1.4; + text-decoration: none; + vertical-align: middle; + transition: transform .1s ease-in-out, background-color .1s ease-in-out; } -.help { - width: 440px; - margin: auto; +.site-page .install-button:hover { background: #44444d; transform: scale(1.03); } +.secondary-button { display: inline-block; padding: 8px 20px; border: 1px solid #303035; border-radius: 4px; background: #ffffff99; font-size: 14px; font-weight: 700; text-decoration: none; } +.secondary-button:hover { background: #ffffffe6; } +#messages { min-height: 1.7em; margin: 12px 0 0; font-size: 14px; } +.team-count { min-height: 1.7em; margin: 8px 0 0; font-size: 13px; } +.has-error { color: #811c1c; } +.hero-preview { max-width: 691px; margin: 32px auto 0; } +.hero-preview img { display: block; width: 100%; height: auto; border: 1px solid #000; filter: drop-shadow(0 16px 24px #22222620); } +.hero-preview figcaption { margin-top: 20px; font-size: 13px; } +.homepage-section { padding: 64px 0; } +.homepage-section h2 { margin: 0 0 12px; font-size: 26px; letter-spacing: .04em; text-transform: uppercase; } +.section-intro { margin: 0 0 32px; font: italic 22px/1.5 Georgia, serif; } +.setup-steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 36px; margin: 32px 0; padding: 0; list-style: none; counter-reset: step; text-align: left; } +.setup-steps li { counter-increment: step; } +.setup-steps li::before { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; color: #fff; background: #222226; content: counter(step); font-weight: 700; } +.setup-steps h3 { margin: 16px 0 8px; font-size: 18px; } +.setup-steps p { margin: 0; } +.bot-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); gap: 20px; margin: 32px 0 0; padding: 0; list-style: none; text-align: left; } +.bot-card { display: flex; flex-direction: column; align-items: center; padding: 24px; border: 1px solid #25252b33; border-radius: 8px; background: #ffffffb3; text-align: center; } +.bot-logo { display: block; align-self: center; width: 72px; height: 72px; margin-bottom: 20px; object-fit: contain; } +.bot-card h3 { margin: 0; font-size: 18px; } +.bot-card p { flex: 1; margin: 12px 0 20px; font-size: 14px; } +code { padding: 2px 4px; border-radius: 3px; background: #ffffffb3; font-size: .95em; overflow-wrap: anywhere; } +.install-cta { margin: 28px 0; } +.site-footer { padding: 28px 0; border-top: 1px solid #25252b33; font-size: 13px; overflow-wrap: anywhere; } +.content-page { max-width: 860px; padding: 40px 0; text-align: left; overflow-wrap: anywhere; } +.content-page h1 { margin-bottom: 28px; } +.content-page h3, .content-page h4 { margin-top: 28px; line-height: 1.4; } +.help-content { max-width: 860px; padding-bottom: 40px; text-align: left; } +.help-header { padding: 40px 0 16px; } +.help-section { margin: 32px 0; } +.help-section h2 { font-size: 24px; } +.getting-started-steps { padding-left: 24px; } +.getting-started-steps li { margin: 14px 0; padding-left: 6px; } +.help-topic { padding: 16px 0; border-bottom: 1px solid #25252b33; } +.help-topic summary { cursor: pointer; font-weight: 700; } +.help-topic pre { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 14px; } + +@media (max-width: 700px) { + .page-container { width: calc(100% - 32px); } + .nav-inner { flex-direction: column; gap: 8px; padding: 12px 0; } + .nav-links { gap: 8px 16px; } + .nav-links a { font-size: 10px; } + .site-page [id] { scroll-margin-top: 128px; } + .hero { padding-top: 32px; } + .hero-intro { flex-direction: column; align-items: center; gap: 16px; text-align: center; } + .hero-logo { width: 88px; margin-top: 0; } + .site-page h1 { font-size: 40px; } + .hero-tagline { font-size: 25px; } + .hero-description { font-size: 16px; } + .hero-actions { justify-content: center; } + .homepage-section { padding: 40px 0; } + .setup-steps { grid-template-columns: 1fr; gap: 28px; } } -.help pre { - text-align: left; +@media (prefers-reduced-motion: reduce) { + .site-page .install-button { transition: none; } + .site-page .install-button:hover { transform: none; } } diff --git a/spec/integration/help_spec.rb b/spec/integration/help_spec.rb index 809a7dd..ecaf023 100644 --- a/spec/integration/help_spec.rb +++ b/spec/integration/help_spec.rb @@ -10,4 +10,13 @@ it 'renders help' do expect(title).to eq('Shell: Help') end + + it 'expands the command reference with the keyboard' do + summary = find('summary', text: 'Command reference') + expect(page).to have_no_css('pre', visible: :visible) + summary.send_keys(:enter) + expect(page).to have_css('pre', text: SlackShellbot::Commands::Man::HELP.strip) + summary.send_keys(:enter) + expect(page).to have_no_css('pre', visible: :visible) + end end diff --git a/spec/integration/index_spec.rb b/spec/integration/index_spec.rb index cfff78b..3d1d1be 100644 --- a/spec/integration/index_spec.rb +++ b/spec/integration/index_spec.rb @@ -16,4 +16,50 @@ expect(title).to eq('Shell: A Unix Shell in Every Slack Channel') expect(first('p[id=register] a')['href']).to eq 'https://slack.com/oauth/v2/authorize?scope=chat:write,im:history,mpim:history,channels:history,groups:history&client_id=slack-client-id' end + + it 'links to the joke FAQ with its answer visible' do + click_link 'Is this a joke?' + expect(page).to have_current_path('/help.html', ignore_query: true) + expect(page).to have_css('#is-this-a-joke p', text: 'Yes. Also, it works!') + end + + it 'links to the bot recommendations after How to use in navigation' do + expect(page).to have_css('.nav-links a[href="/#how-to-use"] + a[href="/#more-bots"]', text: /More bots/i) + within('nav') { click_link 'More bots' } + expect(page).to have_css('#more-bots h3', text: 'Strava in Slack') + end + + it 'promotes the other bots above the open-source section' do + expect(page).to have_css('#more-bots + #open-source') + within('#more-bots') do + { + 'Slava' => 'https://slava.playplay.io/', + 'Strada' => 'https://strada.playplay.io/', + 'Gamebot' => 'https://gamebot2.playplay.io/', + "S'Up" => 'https://sup2.playplay.io/' + }.each do |name, url| + expect(page).to have_link("Meet #{name}", href: url) + end + end + end + + it "loads the promoted bots' original logos" do + find_by_id('more-bots').execute_script("this.scrollIntoView({block: 'center'})") + expect(page).to have_css('#more-bots img.bot-logo', count: 4) do |image| + image.evaluate_script('this.complete && this.naturalWidth > 0') + end + expect(all('#more-bots h3').map(&:text)).to eq(['Strava in Slack', 'Strava in Discord', "S'Up for Slack", 'Leaderboards in Slack']) + expect(page).to have_css('#bots-title', text: /More bots for Slack and Discord/i) + end + + it 'keeps navigation and installation usable on small screens' do + page.execute_script("document.body.innerHTML = ''") + within_frame('mobile') do + expect(page.evaluate_script('window.innerWidth')).to eq(320) + expect(page.evaluate_script('document.documentElement.scrollWidth <= window.innerWidth')).to be true + expect(page).to have_link('Add to Slack', count: 2) + within('nav') { click_link 'Help' } + expect(page).to have_css('h1', text: 'Shell: Help') + end + end end