diff --git a/lib/diaspora/taggable.rb b/lib/diaspora/taggable.rb
index 0f0d43df80b6eb48ac82700b2e8fa36236cd0ed6..388fc56d7bf370ee17f8841320df1c1b195a5193 100644
--- a/lib/diaspora/taggable.rb
+++ b/lib/diaspora/taggable.rb
@@ -4,8 +4,6 @@
 
 module Diaspora
   module Taggable
-    VALID_TAG_BODY = /[^_,\s#*\[\]()\@\/"'\.%]+\b/
-
     def self.included(model)
       model.class_eval do
         cattr_accessor :field_with_tags
@@ -27,10 +25,11 @@ module Diaspora
     end
 
     def tag_strings
-      regex = /(?:^|\s)#(#{VALID_TAG_BODY})/
-      matches = self.send(self.class.field_with_tags).scan(regex).map do |match|
-        match.last
-      end
+      regex = /(?:^|\s)#([\w-]+|<3)/
+      matches = self.
+        send( self.class.field_with_tags ).
+        scan(regex).
+        map { |match| match[0] }
       unique_matches = matches.inject(Hash.new) do |h,element|
         h[element.downcase] = element unless h[element.downcase]
         h
@@ -39,13 +38,20 @@ module Diaspora
     end
 
     def self.format_tags(text, opts={})
-      return text if opts[:plain_text]
+      return text  if opts[:plain_text]
+
       text = ERB::Util.h(text) unless opts[:no_escape]
-      regex = /(^|\s|>)#(#{VALID_TAG_BODY})/
-      form_message = text.to_str.gsub(regex) do |matched_string|
-        "#{$~[1]}<a href=\"/tags/#{$~[2]}\" class=\"tag\">##{$~[2]}</a>"
-      end
-      form_message.html_safe
+      regex = /(^|\s|>)#([\w-]+|&lt;3)/
+
+      text.to_str.gsub(regex) { |matched_string|
+        pre, url_bit, clickable = $1, $2, "##{$2}"
+        if $2 == '&lt;3'
+          # Special case for love, because the world needs more love.
+          url_bit = '<3'
+        end
+
+        %{#{pre}<a href="/tags/#{url_bit}" class="tag">#{clickable}</a>}
+      }.html_safe
     end
   end
 end
diff --git a/spec/shared_behaviors/taggable.rb b/spec/shared_behaviors/taggable.rb
index 2eb2d95c3877517eb4f23c19f3a888d4ccbda2dd..cec1e60fa4135c0e413008fc8bab33d609f7e2dc 100644
--- a/spec/shared_behaviors/taggable.rb
+++ b/spec/shared_behaviors/taggable.rb
@@ -12,6 +12,10 @@ describe Diaspora::Taggable do
     def controller
     end
 
+    def tag_link(s)
+      link_to  "##{s}", "/tags/#{s}", :class => 'tag'
+    end
+
     describe '.format_tags' do
       before do
         @str = '#what #hey #vöglein'
@@ -19,14 +23,63 @@ describe Diaspora::Taggable do
         @object.build_tags
         @object.save!
       end
+
       it 'links the tag to /p' do
-        link = link_to('#vöglein', '/tags/vöglein', :class => 'tag')
-        Diaspora::Taggable.format_tags(@str).should include(link)
+        Diaspora::Taggable.format_tags(@str).should include( tag_link('vöglein') )
       end
+
       it 'responds to plain_text' do
         Diaspora::Taggable.format_tags(@str, :plain_text => true).should == @str
       end
+
+      it "doesn't mangle text when tags are involved" do
+        expected = {
+          nil => '',
+          '' => '',
+          'abc' => 'abc',
+          'a #b c' => "a #{tag_link('b')} c",
+          '#'                      => '#',
+          '##'                     => '##',
+          '###'                    => '###',
+          '#a'                     => tag_link('a'),
+          '#foobar'                => tag_link('foobar'),
+          '#foocar<br>'            => "#{tag_link('foocar')}&lt;br&gt;",
+          '#fooo@oo'               => "#{tag_link('fooo')}@oo",
+          '#num3ric hash tags'     => "#{tag_link('num3ric')} hash tags",
+          '#12345 tag'             => "#{tag_link('12345')} tag",
+          '#12cde tag'             => "#{tag_link('12cde')} tag",
+          '#abc45 tag'             => "#{tag_link('abc45')} tag",
+          '#<3'                    => %{<a href="/tags/<3" class="tag">#&lt;3</a>},
+          'i #<3'                  => %{i <a href="/tags/<3" class="tag">#&lt;3</a>},
+          'i #<3 you'              => %{i <a href="/tags/<3" class="tag">#&lt;3</a> you},
+          '#<4'                    => '#&lt;4',
+          'test#foo test'          => 'test#foo test',
+          'test.#joo bar'          => 'test.#joo bar',
+          'test #foodar test'      => "test #{tag_link('foodar')} test",
+          'test #foofar<br> test'  => "test #{tag_link('foofar')}&lt;br&gt; test",
+          'test #gooo@oo test'     => "test #{tag_link('gooo')}@oo test",
+          'test #foo-test test'    => "test #{tag_link('foo-test')} test",
+          'test #hoo'              => "test #{tag_link('hoo')}",
+          'test #two_word tags'    => "test #{tag_link('two_word')} tags",
+          'test #three_word_tags'  => "test #{tag_link('three_word_tags')}",
+          '#terminal_underscore_'  => tag_link('terminal_underscore_'),
+          '#terminalunderscore_'   => tag_link('terminalunderscore_'),
+          '#_initialunderscore'    => tag_link('_initialunderscore'),
+          '#_initial_underscore'   => tag_link('_initial_underscore'),
+          '#terminalhyphen-'       => tag_link('terminalhyphen-'),
+          '#terminal-hyphen-'      => tag_link('terminal-hyphen-'),
+          '#terminalhyphen- tag'   => "#{tag_link('terminalhyphen-')} tag",
+          '#-initialhyphen'        => tag_link('-initialhyphen'),
+          '#-initialhyphen tag'    => "#{tag_link('-initialhyphen')} tag",
+          '#-initial-hyphen'       => tag_link('-initial-hyphen'),
+        }
+
+        expected.each do |input,output|
+          Diaspora::Taggable.format_tags(input).should == output
+        end
+      end
     end
+
     describe '#build_tags' do
       it 'builds the tags' do
         @object.send(@object.class.field_with_tags_setter, '#what')
@@ -37,14 +90,60 @@ describe Diaspora::Taggable do
         }.should change{@object.tags.count}.by(1)
       end
     end
+
     describe '#tag_strings' do
       it 'returns a string for every #thing' do
         str = '#what #hey #that"smybike. #@hey ##boo # #THATWASMYBIKE #vöglein #hey#there #135440we #abc/23 ### #h!gh #ok? #see: #re:publica'
-        arr = ['what', 'hey', 'that', 'THATWASMYBIKE', 'vöglein', '135440we', 'abc', 'h!gh', 'ok', 'see', 're:publica']
+        arr = ['what', 'hey', 'that', 'THATWASMYBIKE', 'vöglein', '135440we', 'abc', 'h', 'ok', 'see', 're']
 
         @object.send(@object.class.field_with_tags_setter, str)
         @object.tag_strings.should =~ arr
       end
+
+      it 'extracts tags despite surrounding text' do
+        expected = {
+          ''                       => nil,
+          '#'                      => nil,
+          '##'                     => nil,
+          '###'                    => nil,
+          '#a'                     => 'a',
+          '#foobar'                => 'foobar',
+          '#foocar<br>'            => 'foocar',
+          '#fooo@oo'               => 'fooo',
+          '#num3ric hash tags'     => 'num3ric',
+          '#12345 tag'             => '12345',
+          '#12cde tag'             => '12cde',
+          '#abc45 tag'             => 'abc45',
+          '#<3'                    => '<3',
+          '#<4'                    => nil,
+          'test#foo test'          => nil,
+          'test.#joo bar'          => nil,
+          'test #foodar test'      => 'foodar',
+          'test #foofar<br> test'  => 'foofar',
+          'test #gooo@oo test'     => 'gooo',
+          'test #<3 test'          => '<3',
+          'test #foo-test test'    => 'foo-test',
+          'test #hoo'              => 'hoo',
+          'test #two_word tags'    => 'two_word',
+          'test #three_word_tags'  => 'three_word_tags',
+          '#terminal_underscore_'  => 'terminal_underscore_',
+          '#terminalunderscore_'   => 'terminalunderscore_',
+          '#_initialunderscore'    => '_initialunderscore',
+          '#_initial_underscore'   => '_initial_underscore',
+          '#terminalhyphen-'       => 'terminalhyphen-',
+          '#terminal-hyphen-'      => 'terminal-hyphen-',
+          '#terminalhyphen- tag'   => 'terminalhyphen-',
+          '#-initialhyphen'        => '-initialhyphen',
+          '#-initialhyphen tag'    => '-initialhyphen',
+          '#-initial-hyphen'       => '-initial-hyphen',
+        }
+
+        expected.each do |text,hashtag|
+          @object.send  @object.class.field_with_tags_setter, text
+          @object.tag_strings.should == [hashtag].compact
+        end
+      end
+
       it 'returns no duplicates' do
         str = '#what #what #what #whaaaaaaaaaat'
         arr = ['what','whaaaaaaaaaat']
@@ -52,6 +151,7 @@ describe Diaspora::Taggable do
         @object.send(@object.class.field_with_tags_setter, str)
         @object.tag_strings.should =~ arr
       end
+
       it 'is case insensitive' do
         str = '#what #wHaT #WHAT'
         arr = ['what']